CipherTest.java revision 1674:845fefff00a4
1/* 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 != null) && (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} 446