1 /*
2  * Copyright (c) 2002, 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 import java.io.*;
25 import java.net.*;
26 import java.util.*;
27 import java.util.concurrent.*;
28 
29 import java.security.*;
30 import java.security.cert.*;
31 
32 import 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  */
44 public 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 
Server(CipherTest cipherTest)69         Server(CipherTest cipherTest) throws Exception {
70             this.cipherTest = cipherTest;
71         }
72 
73         @Override
run()74         public abstract void run();
75 
handleRequest(InputStream in, OutputStream out)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         CipherSuite cipherSuite;
109         Protocol protocol;
110         String clientAuth;
111 
TestParameters(CipherSuite cipherSuite, Protocol protocol, String clientAuth)112         TestParameters(CipherSuite cipherSuite, Protocol protocol,
113                 String clientAuth) {
114             this.cipherSuite = cipherSuite;
115             this.protocol = protocol;
116             this.clientAuth = clientAuth;
117         }
118 
isEnabled()119         boolean isEnabled() {
120             return cipherSuite.supportedByProtocol(protocol);
121         }
122 
123         @Override
toString()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 
133     private List<TestParameters> tests;
134     private Iterator<TestParameters> testIterator;
135     private SSLSocketFactory factory;
136     private boolean failed;
137 
CipherTest(PeerFactory peerFactory)138     private CipherTest(PeerFactory peerFactory) throws IOException {
139         factory = (SSLSocketFactory)SSLSocketFactory.getDefault();
140         SSLSocket socket = (SSLSocket)factory.createSocket();
141         String[] cipherSuites = socket.getSupportedCipherSuites();
142         String[] protocols = socket.getSupportedProtocols();
143         String[] clientAuths = {null, "RSA", "DSA", "ECDSA"};
144         tests = new ArrayList<TestParameters>(
145             cipherSuites.length * protocols.length * clientAuths.length);
146         for (int i = 0; i < cipherSuites.length; i++) {
147             String cipherSuite = cipherSuites[i];
148 
149             for (int j = 0; j < protocols.length; j++) {
150                 String protocol = protocols[j];
151 
152                 if (!peerFactory.isSupported(cipherSuite, protocol)) {
153                     continue;
154                 }
155 
156                 for (int k = 0; k < clientAuths.length; k++) {
157                     String clientAuth = clientAuths[k];
158                     // no client with anonymous cipher suites.
159                     // TLS_EMPTY_RENEGOTIATION_INFO_SCSV always be skipped.
160                     // TLS 1.3 is skipped due to the signature algorithm,
161                     // exactly MD5withRSA, in the certificates is not allowed.
162                     if ((clientAuth != null && cipherSuite.contains("DH_anon")
163                             || cipherSuite.equals(
164                                     CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV.name())
165                             || "TLSv1.3".equals(protocol))) {
166                         continue;
167                     }
168 
169                     tests.add(new TestParameters(
170                             CipherSuite.cipherSuite(cipherSuite),
171                             Protocol.protocol(protocol),
172                             clientAuth));
173                 }
174             }
175         }
176 
177         testIterator = tests.iterator();
178     }
179 
setFailed()180     synchronized void setFailed() {
181         failed = true;
182     }
183 
run()184     public void run() throws Exception {
185         Thread[] threads = new Thread[THREADS];
186         for (int i = 0; i < THREADS; i++) {
187             try {
188                 threads[i] = new Thread(peerFactory.newClient(this),
189                     "Client " + i);
190             } catch (Exception e) {
191                 e.printStackTrace();
192                 return;
193             }
194             threads[i].start();
195         }
196 
197         // The client threads are ready.
198         clientCondition.countDown();
199 
200         try {
201             for (int i = 0; i < THREADS; i++) {
202                 threads[i].join();
203             }
204         } catch (InterruptedException e) {
205             setFailed();
206             e.printStackTrace();
207         }
208         if (failed) {
209             throw new Exception("*** Test '" + peerFactory.getName() +
210                 "' failed ***");
211         } else {
212             System.out.println("Test '" + peerFactory.getName() +
213                 "' completed successfully");
214         }
215     }
216 
getTest()217     synchronized TestParameters getTest() {
218         if (failed) {
219             return null;
220         }
221         if (testIterator.hasNext()) {
222             return (TestParameters)testIterator.next();
223         }
224         return null;
225     }
226 
getFactory()227     SSLSocketFactory getFactory() {
228         return factory;
229     }
230 
231     static abstract class Client implements Runnable {
232 
233         final CipherTest cipherTest;
234 
Client(CipherTest cipherTest)235         Client(CipherTest cipherTest) throws Exception {
236             this.cipherTest = cipherTest;
237         }
238 
239         @Override
run()240         public final void run() {
241             while (true) {
242                 TestParameters params = cipherTest.getTest();
243                 if (params == null) {
244                     // no more tests
245                     break;
246                 }
247                 if (params.isEnabled() == false) {
248                     System.out.println("Skipping disabled test " + params);
249                     continue;
250                 }
251                 try {
252                     runTest(params);
253                     System.out.println("Passed " + params);
254                 } catch (SocketTimeoutException ste) {
255                     System.out.println("The client connects to the server timeout, "
256                             + "so ignore the test.");
257                     break;
258                 } catch (Exception e) {
259                     cipherTest.setFailed();
260                     System.out.println("** Failed " + params + "**");
261                     e.printStackTrace();
262                 }
263             }
264         }
265 
runTest(TestParameters params)266         abstract void runTest(TestParameters params) throws Exception;
267 
sendRequest(InputStream in, OutputStream out)268         void sendRequest(InputStream in, OutputStream out) throws IOException {
269             out.write("GET / HTTP/1.0\r\n\r\n".getBytes());
270             out.flush();
271             StringBuilder sb = new StringBuilder();
272             while (true) {
273                 int ch = in.read();
274                 if (ch < 0) {
275                     break;
276                 }
277                 sb.append((char)ch);
278             }
279             String response = sb.toString();
280             if (response.startsWith("HTTP/1.0 200 ") == false) {
281                 throw new IOException("Invalid response: " + response);
282             }
283         }
284 
285     }
286 
287     // for some reason, ${test.src} has a different value when the
288     // test is called from the script and when it is called directly...
289     static String pathToStores = ".";
290     static String pathToStoresSH = ".";
291     static String keyStoreFile = "keystore";
292     static String trustStoreFile = "truststore";
293     static char[] passwd = "passphrase".toCharArray();
294 
295     static File PATH;
296 
readKeyStore(String name)297     private static KeyStore readKeyStore(String name) throws Exception {
298         File file = new File(PATH, name);
299         KeyStore ks;
300         try (InputStream in = new FileInputStream(file)) {
301             ks = KeyStore.getInstance("JKS");
302             ks.load(in, passwd);
303         }
304         return ks;
305     }
306 
main(PeerFactory peerFactory, String[] args)307     public static void main(PeerFactory peerFactory, String[] args)
308             throws Exception {
309         long time = System.currentTimeMillis();
310         String relPath;
311         if ((args != null) && (args.length > 0) && args[0].equals("sh")) {
312             relPath = pathToStoresSH;
313         } else {
314             relPath = pathToStores;
315         }
316         PATH = new File(TEST_SRC, relPath);
317         CipherTest.peerFactory = peerFactory;
318         System.out.print(
319             "Initializing test '" + peerFactory.getName() + "'...");
320         secureRandom = new SecureRandom();
321         secureRandom.nextInt();
322         trustStore = readKeyStore(trustStoreFile);
323         keyStore = readKeyStore(keyStoreFile);
324         KeyManagerFactory keyFactory =
325             KeyManagerFactory.getInstance(
326                 KeyManagerFactory.getDefaultAlgorithm());
327         keyFactory.init(keyStore, passwd);
328         keyManager = (X509ExtendedKeyManager)keyFactory.getKeyManagers()[0];
329         trustManager = new AlwaysTrustManager();
330 
331         CipherTest cipherTest = new CipherTest(peerFactory);
332         Thread serverThread = new Thread(peerFactory.newServer(cipherTest),
333             "Server");
334         serverThread.setDaemon(true);
335         serverThread.start();
336         System.out.println("Done");
337         cipherTest.run();
338         time = System.currentTimeMillis() - time;
339         System.out.println("Done. (" + time + " ms)");
340     }
341 
342     static abstract class PeerFactory {
343 
getName()344         abstract String getName();
345 
newClient(CipherTest cipherTest)346         abstract Client newClient(CipherTest cipherTest) throws Exception;
347 
newServer(CipherTest cipherTest)348         abstract Server newServer(CipherTest cipherTest) throws Exception;
349 
isSupported(String cipherSuite, String protocol)350         boolean isSupported(String cipherSuite, String protocol) {
351             // skip kerberos cipher suites
352             if (cipherSuite.startsWith("TLS_KRB5")) {
353                 System.out.println("Skipping unsupported test for " +
354                                     cipherSuite + " of " + protocol);
355                 return false;
356             }
357 
358             // skip SSLv2Hello protocol
359             if (protocol.equals("SSLv2Hello")) {
360                 System.out.println("Skipping unsupported test for " +
361                                     cipherSuite + " of " + protocol);
362                 return false;
363             }
364 
365             // ignore exportable cipher suite for TLSv1.1
366             if (protocol.equals("TLSv1.1")) {
367                 if (cipherSuite.indexOf("_EXPORT_WITH") != -1) {
368                     System.out.println("Skipping obsoleted test for " +
369                                         cipherSuite + " of " + protocol);
370                     return false;
371                 }
372             }
373 
374             return true;
375         }
376     }
377 
378 }
379 
380 // we currently don't do any chain verification. we assume that works ok
381 // and we can speed up the test. we could also just add a plain certificate
382 // chain comparision with our trusted certificates.
383 class AlwaysTrustManager implements X509TrustManager {
384 
AlwaysTrustManager()385     public AlwaysTrustManager() {
386 
387     }
388 
389     @Override
checkClientTrusted(X509Certificate[] chain, String authType)390     public void checkClientTrusted(X509Certificate[] chain, String authType)
391             throws CertificateException {
392         // empty
393     }
394 
395     @Override
checkServerTrusted(X509Certificate[] chain, String authType)396     public void checkServerTrusted(X509Certificate[] chain, String authType)
397             throws CertificateException {
398         // empty
399     }
400 
401     @Override
getAcceptedIssuers()402     public X509Certificate[] getAcceptedIssuers() {
403         return new X509Certificate[0];
404     }
405 }
406 
407 class MyX509KeyManager extends X509ExtendedKeyManager {
408 
409     private final X509ExtendedKeyManager keyManager;
410     private String authType;
411 
MyX509KeyManager(X509ExtendedKeyManager keyManager)412     MyX509KeyManager(X509ExtendedKeyManager keyManager) {
413         this.keyManager = keyManager;
414     }
415 
setAuthType(String authType)416     void setAuthType(String authType) {
417         this.authType = "ECDSA".equals(authType) ? "EC" : authType;
418     }
419 
420     @Override
getClientAliases(String keyType, Principal[] issuers)421     public String[] getClientAliases(String keyType, Principal[] issuers) {
422         if (authType == null) {
423             return null;
424         }
425         return keyManager.getClientAliases(authType, issuers);
426     }
427 
428     @Override
chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket)429     public String chooseClientAlias(String[] keyType, Principal[] issuers,
430             Socket socket) {
431         if (authType == null) {
432             return null;
433         }
434         return keyManager.chooseClientAlias(new String[] {authType},
435             issuers, socket);
436     }
437 
438     @Override
chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine)439     public String chooseEngineClientAlias(String[] keyType,
440             Principal[] issuers, SSLEngine engine) {
441         if (authType == null) {
442             return null;
443         }
444         return keyManager.chooseEngineClientAlias(new String[] {authType},
445             issuers, engine);
446     }
447 
448     @Override
getServerAliases(String keyType, Principal[] issuers)449     public String[] getServerAliases(String keyType, Principal[] issuers) {
450         throw new UnsupportedOperationException("Servers not supported");
451     }
452 
453     @Override
chooseServerAlias(String keyType, Principal[] issuers, Socket socket)454     public String chooseServerAlias(String keyType, Principal[] issuers,
455             Socket socket) {
456         throw new UnsupportedOperationException("Servers not supported");
457     }
458 
459     @Override
chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine)460     public String chooseEngineServerAlias(String keyType, Principal[] issuers,
461             SSLEngine engine) {
462         throw new UnsupportedOperationException("Servers not supported");
463     }
464 
465     @Override
getCertificateChain(String alias)466     public X509Certificate[] getCertificateChain(String alias) {
467         return keyManager.getCertificateChain(alias);
468     }
469 
470     @Override
getPrivateKey(String alias)471     public PrivateKey getPrivateKey(String alias) {
472         return keyManager.getPrivateKey(alias);
473     }
474 
475 }
476 
477 class DaemonThreadFactory implements ThreadFactory {
478 
479     final static ThreadFactory INSTANCE = new DaemonThreadFactory();
480 
481     private final static ThreadFactory DEFAULT = Executors.defaultThreadFactory();
482 
483     @Override
newThread(Runnable r)484     public Thread newThread(Runnable r) {
485         Thread t = DEFAULT.newThread(r);
486         t.setDaemon(true);
487         return t;
488     }
489 
490 }
491