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 int serverPort = 0;
48 
49     final int THREADS;
50 
51     // assume that if we do not read anything for 20 seconds, something
52     // has gone wrong
53     final static int TIMEOUT = 20 * 1000;
54 
55     static KeyStore trustStore, keyStore;
56     static X509ExtendedKeyManager keyManager;
57     static X509TrustManager trustManager;
58     static SecureRandom secureRandom;
59 
60     private static PeerFactory peerFactory;
61 
62     static abstract class Server implements Runnable {
63 
64         final CipherTest cipherTest;
65 
Server(CipherTest cipherTest)66         Server(CipherTest cipherTest) throws Exception {
67             this.cipherTest = cipherTest;
68         }
69 
run()70         public abstract void run();
71 
handleRequest(InputStream in, OutputStream out)72         void handleRequest(InputStream in, OutputStream out) throws IOException {
73             boolean newline = false;
74             StringBuilder sb = new StringBuilder();
75             while (true) {
76                 int ch = in.read();
77                 if (ch < 0) {
78                     throw new EOFException();
79                 }
80                 sb.append((char)ch);
81                 if (ch == '\r') {
82                     // empty
83                 } else if (ch == '\n') {
84                     if (newline) {
85                         // 2nd newline in a row, end of request
86                         break;
87                     }
88                     newline = true;
89                 } else {
90                     newline = false;
91                 }
92             }
93             String request = sb.toString();
94             if (request.startsWith("GET / HTTP/1.") == false) {
95                 throw new IOException("Invalid request: " + request);
96             }
97             out.write("HTTP/1.0 200 OK\r\n\r\n".getBytes());
98         }
99 
100     }
101 
102     public static class TestParameters {
103 
104         CipherSuite cipherSuite;
105         Protocol protocol;
106         String clientAuth;
107 
TestParameters(CipherSuite cipherSuite, Protocol protocol, String clientAuth)108         TestParameters(CipherSuite cipherSuite, Protocol protocol,
109                 String clientAuth) {
110             this.cipherSuite = cipherSuite;
111             this.protocol = protocol;
112             this.clientAuth = clientAuth;
113         }
114 
isEnabled()115         boolean isEnabled() {
116             return cipherSuite.supportedByProtocol(protocol);
117         }
118 
toString()119         public String toString() {
120             String s = cipherSuite + " in " + protocol + " mode";
121             if (clientAuth != null) {
122                 s += " with " + clientAuth + " client authentication";
123             }
124             return s;
125         }
126     }
127 
128     private List<TestParameters> tests;
129     private Iterator<TestParameters> testIterator;
130     private SSLSocketFactory factory;
131     private boolean failed;
132 
CipherTest(PeerFactory peerFactory)133     private CipherTest(PeerFactory peerFactory) throws IOException {
134         THREADS = Integer.parseInt(System.getProperty("numThreads", "4"));
135         factory = (SSLSocketFactory)SSLSocketFactory.getDefault();
136         SSLSocket socket = (SSLSocket)factory.createSocket();
137         String[] cipherSuites = socket.getSupportedCipherSuites();
138         String[] protocols = socket.getSupportedProtocols();
139         String[] clientAuths = {null, "RSA", "DSA"};
140         tests = new ArrayList<TestParameters>(
141             cipherSuites.length * protocols.length * clientAuths.length);
142         for (int j = 0; j < protocols.length; j++) {
143             String protocol = protocols[j];
144             if (protocol.equals(Protocol.SSLV2HELLO.name)) {
145                 System.out.println("Skipping SSLv2Hello protocol");
146                 continue;
147             }
148 
149             for (int i = 0; i < cipherSuites.length; i++) {
150                 String cipherSuite = cipherSuites[i];
151 
152                 // skip kerberos cipher suites and TLS_EMPTY_RENEGOTIATION_INFO_SCSV
153                 if (cipherSuite.startsWith("TLS_KRB5") || cipherSuite.equals(
154                         CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV.name())) {
155                     System.out.println("Skipping unsupported test for " +
156                                         cipherSuite + " of " + protocol);
157                     continue;
158                 }
159 
160                 if (!peerFactory.isSupported(cipherSuite, protocol)) {
161                     continue;
162                 }
163 
164                 for (int k = 0; k < clientAuths.length; k++) {
165                     String clientAuth = clientAuths[k];
166                     // no client with anonymous cipher suites;
167                     // TLS 1.3 doesn't support DSA
168                     if ((clientAuth != null && cipherSuite.contains("DH_anon"))
169                             || ("DSA".equals(clientAuth) && "TLSv1.3".equals(protocol))) {
170                         continue;
171                     }
172                     tests.add(new TestParameters(
173                             CipherSuite.cipherSuite(cipherSuite),
174                             Protocol.protocol(protocol),
175                             clientAuth));
176                 }
177             }
178         }
179         testIterator = tests.iterator();
180     }
181 
setFailed()182     synchronized void setFailed() {
183         failed = true;
184     }
185 
run()186     public void run() throws Exception {
187         Thread[] threads = new Thread[THREADS];
188         for (int i = 0; i < THREADS; i++) {
189             try {
190                 threads[i] = new Thread(peerFactory.newClient(this),
191                     "Client " + i);
192             } catch (Exception e) {
193                 e.printStackTrace();
194                 return;
195             }
196             threads[i].start();
197         }
198         try {
199             for (int i = 0; i < THREADS; i++) {
200                 threads[i].join();
201             }
202         } catch (InterruptedException e) {
203             setFailed();
204             e.printStackTrace();
205         }
206         if (failed) {
207             throw new Exception("*** Test '" + peerFactory.getName() +
208                 "' failed ***");
209         } else {
210             System.out.println("Test '" + peerFactory.getName() +
211                 "' completed successfully");
212         }
213     }
214 
getTest()215     synchronized TestParameters getTest() {
216         if (failed) {
217             return null;
218         }
219         if (testIterator.hasNext()) {
220             return (TestParameters)testIterator.next();
221         }
222         return null;
223     }
224 
getFactory()225     SSLSocketFactory getFactory() {
226         return factory;
227     }
228 
229     static abstract class Client implements Runnable {
230 
231         final CipherTest cipherTest;
232 
Client(CipherTest cipherTest)233         Client(CipherTest cipherTest) throws Exception {
234             this.cipherTest = cipherTest;
235         }
236 
run()237         public final void run() {
238             while (true) {
239                 TestParameters params = cipherTest.getTest();
240                 if (params == null) {
241                     // no more tests
242                     break;
243                 }
244                 if (!params.isEnabled()) {
245                     System.out.println("Skipping disabled test " + params);
246                     continue;
247                 }
248                 try {
249                     runTest(params);
250                     System.out.println("Passed " + params);
251                 } catch (Exception e) {
252                     cipherTest.setFailed();
253                     System.out.println("** Failed " + params + "**");
254                     e.printStackTrace();
255                 }
256             }
257         }
258 
runTest(TestParameters params)259         abstract void runTest(TestParameters params) throws Exception;
260 
sendRequest(InputStream in, OutputStream out)261         void sendRequest(InputStream in, OutputStream out) throws IOException {
262             out.write("GET / HTTP/1.0\r\n\r\n".getBytes());
263             out.flush();
264             StringBuilder sb = new StringBuilder();
265             while (true) {
266                 int ch = in.read();
267                 if (ch < 0) {
268                     break;
269                 }
270                 sb.append((char)ch);
271             }
272             String response = sb.toString();
273             if (response.startsWith("HTTP/1.0 200 ") == false) {
274                 throw new IOException("Invalid response: " + response);
275             }
276         }
277 
278     }
279 
280     // for some reason, ${test.src} has a different value when the
281     // test is called from the script and when it is called directly...
282     static String pathToStores = "../../etc";
283     static String pathToStoresSH = ".";
284     static String keyStoreFile = "keystore";
285     static String trustStoreFile = "truststore";
286     static char[] passwd = "passphrase".toCharArray();
287 
288     static File PATH;
289 
readKeyStore(String name)290     private static KeyStore readKeyStore(String name) throws Exception {
291         File file = new File(PATH, name);
292         InputStream in = new FileInputStream(file);
293         KeyStore ks = KeyStore.getInstance("JKS");
294         ks.load(in, passwd);
295         in.close();
296         return ks;
297     }
298 
main(PeerFactory peerFactory, String[] args)299     public static void main(PeerFactory peerFactory, String[] args)
300             throws Exception {
301         long time = System.currentTimeMillis();
302         String relPath;
303         if ((args != null) && (args.length > 0) && args[0].equals("sh")) {
304             relPath = pathToStoresSH;
305         } else {
306             relPath = pathToStores;
307         }
308         PATH = new File(System.getProperty("test.src", "."), relPath);
309         CipherTest.peerFactory = peerFactory;
310         System.out.println(
311             "Initializing test '" + peerFactory.getName() + "'...");
312         secureRandom = new SecureRandom();
313         secureRandom.nextInt();
314         trustStore = readKeyStore(trustStoreFile);
315         keyStore = readKeyStore(keyStoreFile);
316         KeyManagerFactory keyFactory =
317             KeyManagerFactory.getInstance(
318                 KeyManagerFactory.getDefaultAlgorithm());
319         keyFactory.init(keyStore, passwd);
320         keyManager = (X509ExtendedKeyManager)keyFactory.getKeyManagers()[0];
321         trustManager = new AlwaysTrustManager();
322 
323         CipherTest cipherTest = new CipherTest(peerFactory);
324         Thread serverThread = new Thread(peerFactory.newServer(cipherTest),
325             "Server");
326         serverThread.setDaemon(true);
327         serverThread.start();
328         System.out.println("Done");
329         cipherTest.run();
330         time = System.currentTimeMillis() - time;
331         System.out.println("Done. (" + time + " ms)");
332     }
333 
334     static abstract class PeerFactory {
335 
getName()336         abstract String getName();
337 
newClient(CipherTest cipherTest)338         abstract Client newClient(CipherTest cipherTest) throws Exception;
339 
newServer(CipherTest cipherTest)340         abstract Server newServer(CipherTest cipherTest) throws Exception;
341 
isSupported(String cipherSuite, String protocol)342         boolean isSupported(String cipherSuite, String protocol) {
343             // ignore exportable cipher suite for TLSv1.1
344             if (protocol.equals("TLSv1.1")
345                     && (cipherSuite.indexOf("_EXPORT_WITH") != -1)) {
346                     System.out.println("Skipping obsoleted test for " +
347                                         cipherSuite + " of " + protocol);
348                     return false;
349             }
350 
351             // ignore obsoleted cipher suite for the specified protocol
352             // TODO
353 
354             // ignore unsupported cipher suite for the specified protocol
355             // TODO
356 
357             return true;
358         }
359     }
360 
361 }
362 
363 // we currently don't do any chain verification. we assume that works ok
364 // and we can speed up the test. we could also just add a plain certificate
365 // chain comparision with our trusted certificates.
366 class AlwaysTrustManager implements X509TrustManager {
367 
AlwaysTrustManager()368     public AlwaysTrustManager() {
369 
370     }
371 
checkClientTrusted(X509Certificate[] chain, String authType)372     public void checkClientTrusted(X509Certificate[] chain, String authType)
373             throws CertificateException {
374         // empty
375     }
376 
checkServerTrusted(X509Certificate[] chain, String authType)377     public void checkServerTrusted(X509Certificate[] chain, String authType)
378             throws CertificateException {
379         // empty
380     }
381 
getAcceptedIssuers()382     public X509Certificate[] getAcceptedIssuers() {
383         return new X509Certificate[0];
384     }
385 }
386 
387 class MyX509KeyManager extends X509ExtendedKeyManager {
388 
389     private final X509ExtendedKeyManager keyManager;
390     private String authType;
391 
MyX509KeyManager(X509ExtendedKeyManager keyManager)392     MyX509KeyManager(X509ExtendedKeyManager keyManager) {
393         this.keyManager = keyManager;
394     }
395 
setAuthType(String authType)396     void setAuthType(String authType) {
397         this.authType = authType;
398     }
399 
getClientAliases(String keyType, Principal[] issuers)400     public String[] getClientAliases(String keyType, Principal[] issuers) {
401         if (authType == null) {
402             return null;
403         }
404         return keyManager.getClientAliases(authType, issuers);
405     }
406 
chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket)407     public String chooseClientAlias(String[] keyType, Principal[] issuers,
408             Socket socket) {
409         if (authType == null) {
410             return null;
411         }
412         return keyManager.chooseClientAlias(new String[] {authType},
413             issuers, socket);
414     }
415 
chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine)416     public String chooseEngineClientAlias(String[] keyType,
417             Principal[] issuers, SSLEngine engine) {
418         if (authType == null) {
419             return null;
420         }
421         return keyManager.chooseEngineClientAlias(new String[] {authType},
422             issuers, engine);
423     }
424 
getServerAliases(String keyType, Principal[] issuers)425     public String[] getServerAliases(String keyType, Principal[] issuers) {
426         throw new UnsupportedOperationException("Servers not supported");
427     }
428 
chooseServerAlias(String keyType, Principal[] issuers, Socket socket)429     public String chooseServerAlias(String keyType, Principal[] issuers,
430             Socket socket) {
431         throw new UnsupportedOperationException("Servers not supported");
432     }
433 
chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine)434     public String chooseEngineServerAlias(String keyType, Principal[] issuers,
435             SSLEngine engine) {
436         throw new UnsupportedOperationException("Servers not supported");
437     }
438 
getCertificateChain(String alias)439     public X509Certificate[] getCertificateChain(String alias) {
440         return keyManager.getCertificateChain(alias);
441     }
442 
getPrivateKey(String alias)443     public PrivateKey getPrivateKey(String alias) {
444         return keyManager.getPrivateKey(alias);
445     }
446 
447 }
448 
449 class DaemonThreadFactory implements ThreadFactory {
450 
451     final static ThreadFactory INSTANCE = new DaemonThreadFactory();
452 
453     private final static ThreadFactory DEFAULT = Executors.defaultThreadFactory();
454 
newThread(Runnable r)455     public Thread newThread(Runnable r) {
456         Thread t = DEFAULT.newThread(r);
457         t.setDaemon(true);
458         return t;
459     }
460 
461 }
462