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