CipherTest.java revision 3865:ef5bbbe0dd75
1/* 2 * Copyright (c) 2002, 2011, 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.*; 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 return TLSCipherStatus.isEnabled(cipherSuite, protocol); 118 } 119 120 public String toString() { 121 String s = cipherSuite + " in " + protocol + " mode"; 122 if (clientAuth != null) { 123 s += " with " + clientAuth + " client authentication"; 124 } 125 return s; 126 } 127 128 static enum TLSCipherStatus { 129 // cipher suites supported since TLS 1.2 130 CS_01("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", 0x0303, 0xFFFF), 131 CS_02("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", 0x0303, 0xFFFF), 132 CS_03("TLS_RSA_WITH_AES_256_CBC_SHA256", 0x0303, 0xFFFF), 133 CS_04("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", 0x0303, 0xFFFF), 134 CS_05("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", 0x0303, 0xFFFF), 135 CS_06("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", 0x0303, 0xFFFF), 136 CS_07("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", 0x0303, 0xFFFF), 137 138 CS_08("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF), 139 CS_09("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF), 140 CS_10("TLS_RSA_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF), 141 CS_11("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF), 142 CS_12("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF), 143 CS_13("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF), 144 CS_14("TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF), 145 146 CS_15("TLS_DH_anon_WITH_AES_256_CBC_SHA256", 0x0303, 0xFFFF), 147 CS_16("TLS_DH_anon_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF), 148 CS_17("TLS_RSA_WITH_NULL_SHA256", 0x0303, 0xFFFF), 149 150 // cipher suites obsoleted since TLS 1.2 151 CS_50("SSL_RSA_WITH_DES_CBC_SHA", 0x0000, 0x0303), 152 CS_51("SSL_DHE_RSA_WITH_DES_CBC_SHA", 0x0000, 0x0303), 153 CS_52("SSL_DHE_DSS_WITH_DES_CBC_SHA", 0x0000, 0x0303), 154 CS_53("SSL_DH_anon_WITH_DES_CBC_SHA", 0x0000, 0x0303), 155 CS_54("TLS_KRB5_WITH_DES_CBC_SHA", 0x0000, 0x0303), 156 CS_55("TLS_KRB5_WITH_DES_CBC_MD5", 0x0000, 0x0303), 157 158 // cipher suites obsoleted since TLS 1.1 159 CS_60("SSL_RSA_EXPORT_WITH_RC4_40_MD5", 0x0000, 0x0302), 160 CS_61("SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", 0x0000, 0x0302), 161 CS_62("SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0000, 0x0302), 162 CS_63("SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0000, 0x0302), 163 CS_64("SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", 0x0000, 0x0302), 164 CS_65("SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA", 0x0000, 0x0302), 165 CS_66("TLS_KRB5_EXPORT_WITH_RC4_40_SHA", 0x0000, 0x0302), 166 CS_67("TLS_KRB5_EXPORT_WITH_RC4_40_MD5", 0x0000, 0x0302), 167 CS_68("TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", 0x0000, 0x0302), 168 CS_69("TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", 0x0000, 0x0302), 169 170 // ignore TLS_EMPTY_RENEGOTIATION_INFO_SCSV always 171 CS_99("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", 0xFFFF, 0x0000); 172 173 // the cipher suite name 174 final String cipherSuite; 175 176 // supported since protocol version 177 final int supportedSince; 178 179 // obsoleted since protocol version 180 final int obsoletedSince; 181 182 TLSCipherStatus(String cipherSuite, 183 int supportedSince, int obsoletedSince) { 184 this.cipherSuite = cipherSuite; 185 this.supportedSince = supportedSince; 186 this.obsoletedSince = obsoletedSince; 187 } 188 189 static boolean isEnabled(String cipherSuite, String protocol) { 190 int versionNumber = toVersionNumber(protocol); 191 192 if (versionNumber < 0) { 193 return true; // unlikely to happen 194 } 195 196 for (TLSCipherStatus status : TLSCipherStatus.values()) { 197 if (cipherSuite.equals(status.cipherSuite)) { 198 if ((versionNumber < status.supportedSince) || 199 (versionNumber >= status.obsoletedSince)) { 200 return false; 201 } 202 203 return true; 204 } 205 } 206 207 return true; 208 } 209 210 private static int toVersionNumber(String protocol) { 211 int versionNumber = -1; 212 213 switch (protocol) { 214 case "SSLv2Hello": 215 versionNumber = 0x0002; 216 break; 217 case "SSLv3": 218 versionNumber = 0x0300; 219 break; 220 case "TLSv1": 221 versionNumber = 0x0301; 222 break; 223 case "TLSv1.1": 224 versionNumber = 0x0302; 225 break; 226 case "TLSv1.2": 227 versionNumber = 0x0303; 228 break; 229 default: 230 // unlikely to happen 231 } 232 233 return versionNumber; 234 } 235 } 236 } 237 238 private List<TestParameters> tests; 239 private Iterator<TestParameters> testIterator; 240 private SSLSocketFactory factory; 241 private boolean failed; 242 243 private CipherTest(PeerFactory peerFactory) throws IOException { 244 THREADS = Integer.parseInt(System.getProperty("numThreads", "4")); 245 factory = (SSLSocketFactory)SSLSocketFactory.getDefault(); 246 SSLSocket socket = (SSLSocket)factory.createSocket(); 247 String[] cipherSuites = socket.getSupportedCipherSuites(); 248 String[] protocols = socket.getSupportedProtocols(); 249 String[] clientAuths = {null, "RSA", "DSA", "ECDSA"}; 250 tests = new ArrayList<TestParameters>( 251 cipherSuites.length * protocols.length * clientAuths.length); 252 for (int i = 0; i < cipherSuites.length; i++) { 253 String cipherSuite = cipherSuites[i]; 254 255 for (int j = 0; j < protocols.length; j++) { 256 String protocol = protocols[j]; 257 258 if (!peerFactory.isSupported(cipherSuite, protocol)) { 259 continue; 260 } 261 262 for (int k = 0; k < clientAuths.length; k++) { 263 String clientAuth = clientAuths[k]; 264 if ((clientAuth != null) && 265 (cipherSuite.indexOf("DH_anon") != -1)) { 266 // no client with anonymous ciphersuites 267 continue; 268 } 269 270 tests.add(new TestParameters(cipherSuite, protocol, 271 clientAuth)); 272 } 273 } 274 } 275 276 testIterator = tests.iterator(); 277 } 278 279 synchronized void setFailed() { 280 failed = true; 281 } 282 283 public void run() throws Exception { 284 Thread[] threads = new Thread[THREADS]; 285 for (int i = 0; i < THREADS; i++) { 286 try { 287 threads[i] = new Thread(peerFactory.newClient(this), 288 "Client " + i); 289 } catch (Exception e) { 290 e.printStackTrace(); 291 return; 292 } 293 threads[i].start(); 294 } 295 try { 296 for (int i = 0; i < THREADS; i++) { 297 threads[i].join(); 298 } 299 } catch (InterruptedException e) { 300 setFailed(); 301 e.printStackTrace(); 302 } 303 if (failed) { 304 throw new Exception("*** Test '" + peerFactory.getName() + 305 "' failed ***"); 306 } else { 307 System.out.println("Test '" + peerFactory.getName() + 308 "' completed successfully"); 309 } 310 } 311 312 synchronized TestParameters getTest() { 313 if (failed) { 314 return null; 315 } 316 if (testIterator.hasNext()) { 317 return (TestParameters)testIterator.next(); 318 } 319 return null; 320 } 321 322 SSLSocketFactory getFactory() { 323 return factory; 324 } 325 326 static abstract class Client implements Runnable { 327 328 final CipherTest cipherTest; 329 330 Client(CipherTest cipherTest) throws Exception { 331 this.cipherTest = cipherTest; 332 } 333 334 public final void run() { 335 while (true) { 336 TestParameters params = cipherTest.getTest(); 337 if (params == null) { 338 // no more tests 339 break; 340 } 341 if (params.isEnabled() == false) { 342 System.out.println("Skipping disabled test " + params); 343 continue; 344 } 345 try { 346 runTest(params); 347 System.out.println("Passed " + params); 348 } catch (Exception e) { 349 cipherTest.setFailed(); 350 System.out.println("** Failed " + params + "**"); 351 e.printStackTrace(); 352 } 353 } 354 } 355 356 abstract void runTest(TestParameters params) throws Exception; 357 358 void sendRequest(InputStream in, OutputStream out) throws IOException { 359 out.write("GET / HTTP/1.0\r\n\r\n".getBytes()); 360 out.flush(); 361 StringBuilder sb = new StringBuilder(); 362 while (true) { 363 int ch = in.read(); 364 if (ch < 0) { 365 break; 366 } 367 sb.append((char)ch); 368 } 369 String response = sb.toString(); 370 if (response.startsWith("HTTP/1.0 200 ") == false) { 371 throw new IOException("Invalid response: " + response); 372 } 373 } 374 375 } 376 377 // for some reason, ${test.src} has a different value when the 378 // test is called from the script and when it is called directly... 379 static String pathToStores = "."; 380 static String pathToStoresSH = "."; 381 static String keyStoreFile = "keystore"; 382 static String trustStoreFile = "truststore"; 383 static char[] passwd = "passphrase".toCharArray(); 384 385 static File PATH; 386 387 private static KeyStore readKeyStore(String name) throws Exception { 388 File file = new File(PATH, name); 389 InputStream in = new FileInputStream(file); 390 KeyStore ks = KeyStore.getInstance("JKS"); 391 ks.load(in, passwd); 392 in.close(); 393 return ks; 394 } 395 396 public static void main(PeerFactory peerFactory, String[] args) 397 throws Exception { 398 long time = System.currentTimeMillis(); 399 String relPath; 400 if ((args != null) && (args.length > 0) && args[0].equals("sh")) { 401 relPath = pathToStoresSH; 402 } else { 403 relPath = pathToStores; 404 } 405 PATH = new File(System.getProperty("test.src", "."), relPath); 406 CipherTest.peerFactory = peerFactory; 407 System.out.print( 408 "Initializing test '" + peerFactory.getName() + "'..."); 409 secureRandom = new SecureRandom(); 410 secureRandom.nextInt(); 411 trustStore = readKeyStore(trustStoreFile); 412 keyStore = readKeyStore(keyStoreFile); 413 KeyManagerFactory keyFactory = 414 KeyManagerFactory.getInstance( 415 KeyManagerFactory.getDefaultAlgorithm()); 416 keyFactory.init(keyStore, passwd); 417 keyManager = (X509ExtendedKeyManager)keyFactory.getKeyManagers()[0]; 418 trustManager = new AlwaysTrustManager(); 419 420 CipherTest cipherTest = new CipherTest(peerFactory); 421 Thread serverThread = new Thread(peerFactory.newServer(cipherTest), 422 "Server"); 423 serverThread.setDaemon(true); 424 serverThread.start(); 425 System.out.println("Done"); 426 cipherTest.run(); 427 time = System.currentTimeMillis() - time; 428 System.out.println("Done. (" + time + " ms)"); 429 } 430 431 static abstract class PeerFactory { 432 433 abstract String getName(); 434 435 abstract Client newClient(CipherTest cipherTest) throws Exception; 436 437 abstract Server newServer(CipherTest cipherTest) throws Exception; 438 439 boolean isSupported(String cipherSuite, String protocol) { 440 // skip kerberos cipher suites 441 if (cipherSuite.startsWith("TLS_KRB5")) { 442 System.out.println("Skipping unsupported test for " + 443 cipherSuite + " of " + protocol); 444 return false; 445 } 446 447 // skip SSLv2Hello protocol 448 if (protocol.equals("SSLv2Hello")) { 449 System.out.println("Skipping unsupported test for " + 450 cipherSuite + " of " + protocol); 451 return false; 452 } 453 454 // ignore exportable cipher suite for TLSv1.1 455 if (protocol.equals("TLSv1.1")) { 456 if (cipherSuite.indexOf("_EXPORT_WITH") != -1) { 457 System.out.println("Skipping obsoleted test for " + 458 cipherSuite + " of " + protocol); 459 return false; 460 } 461 } 462 463 return true; 464 } 465 } 466 467} 468 469// we currently don't do any chain verification. we assume that works ok 470// and we can speed up the test. we could also just add a plain certificate 471// chain comparision with our trusted certificates. 472class AlwaysTrustManager implements X509TrustManager { 473 474 public AlwaysTrustManager() { 475 476 } 477 478 public void checkClientTrusted(X509Certificate[] chain, String authType) 479 throws CertificateException { 480 // empty 481 } 482 483 public void checkServerTrusted(X509Certificate[] chain, String authType) 484 throws CertificateException { 485 // empty 486 } 487 488 public X509Certificate[] getAcceptedIssuers() { 489 return new X509Certificate[0]; 490 } 491} 492 493class MyX509KeyManager extends X509ExtendedKeyManager { 494 495 private final X509ExtendedKeyManager keyManager; 496 private String authType; 497 498 MyX509KeyManager(X509ExtendedKeyManager keyManager) { 499 this.keyManager = keyManager; 500 } 501 502 void setAuthType(String authType) { 503 this.authType = "ECDSA".equals(authType) ? "EC" : authType; 504 } 505 506 public String[] getClientAliases(String keyType, Principal[] issuers) { 507 if (authType == null) { 508 return null; 509 } 510 return keyManager.getClientAliases(authType, issuers); 511 } 512 513 public String chooseClientAlias(String[] keyType, Principal[] issuers, 514 Socket socket) { 515 if (authType == null) { 516 return null; 517 } 518 return keyManager.chooseClientAlias(new String[] {authType}, 519 issuers, socket); 520 } 521 522 public String chooseEngineClientAlias(String[] keyType, 523 Principal[] issuers, SSLEngine engine) { 524 if (authType == null) { 525 return null; 526 } 527 return keyManager.chooseEngineClientAlias(new String[] {authType}, 528 issuers, engine); 529 } 530 531 public String[] getServerAliases(String keyType, Principal[] issuers) { 532 throw new UnsupportedOperationException("Servers not supported"); 533 } 534 535 public String chooseServerAlias(String keyType, Principal[] issuers, 536 Socket socket) { 537 throw new UnsupportedOperationException("Servers not supported"); 538 } 539 540 public String chooseEngineServerAlias(String keyType, Principal[] issuers, 541 SSLEngine engine) { 542 throw new UnsupportedOperationException("Servers not supported"); 543 } 544 545 public X509Certificate[] getCertificateChain(String alias) { 546 return keyManager.getCertificateChain(alias); 547 } 548 549 public PrivateKey getPrivateKey(String alias) { 550 return keyManager.getPrivateKey(alias); 551 } 552 553} 554 555class DaemonThreadFactory implements ThreadFactory { 556 557 final static ThreadFactory INSTANCE = new DaemonThreadFactory(); 558 559 private final static ThreadFactory DEFAULT = Executors.defaultThreadFactory(); 560 561 public Thread newThread(Runnable r) { 562 Thread t = DEFAULT.newThread(r); 563 t.setDaemon(true); 564 return t; 565 } 566 567} 568