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"}; 250 String[] clientAuths = {null}; 251 tests = new ArrayList<TestParameters>( 252 cipherSuites.length * protocols.length * clientAuths.length); 253 for (int i = 0; i < cipherSuites.length; i++) { 254 String cipherSuite = cipherSuites[i]; 255 256 for (int j = 0; j < protocols.length; j++) { 257 String protocol = protocols[j]; 258 259 if (!peerFactory.isSupported(cipherSuite, protocol)) { 260 continue; 261 } 262 263 for (int k = 0; k < clientAuths.length; k++) { 264 String clientAuth = clientAuths[k]; 265 if ((clientAuth != null) && 266 (cipherSuite.indexOf("DH_anon") != -1)) { 267 // no client with anonymous ciphersuites 268 continue; 269 } 270 tests.add(new TestParameters(cipherSuite, protocol, 271 clientAuth)); 272 } 273 } 274 } 275 testIterator = tests.iterator(); 276 } 277 278 synchronized void setFailed() { 279 failed = true; 280 } 281 282 public void run() throws Exception { 283 Thread[] threads = new Thread[THREADS]; 284 for (int i = 0; i < THREADS; i++) { 285 try { 286 threads[i] = new Thread(peerFactory.newClient(this), 287 "Client " + i); 288 } catch (Exception e) { 289 e.printStackTrace(); 290 return; 291 } 292 threads[i].start(); 293 } 294 try { 295 for (int i = 0; i < THREADS; i++) { 296 threads[i].join(); 297 } 298 } catch (InterruptedException e) { 299 setFailed(); 300 e.printStackTrace(); 301 } 302 if (failed) { 303 throw new Exception("*** Test '" + peerFactory.getName() + 304 "' failed ***"); 305 } else { 306 System.out.println("Test '" + peerFactory.getName() + 307 "' completed successfully"); 308 } 309 } 310 311 synchronized TestParameters getTest() { 312 if (failed) { 313 return null; 314 } 315 if (testIterator.hasNext()) { 316 return (TestParameters)testIterator.next(); 317 } 318 return null; 319 } 320 321 SSLSocketFactory getFactory() { 322 return factory; 323 } 324 325 static abstract class Client implements Runnable { 326 327 final CipherTest cipherTest; 328 329 Client(CipherTest cipherTest) throws Exception { 330 this.cipherTest = cipherTest; 331 } 332 333 public final void run() { 334 while (true) { 335 TestParameters params = cipherTest.getTest(); 336 if (params == null) { 337 // no more tests 338 break; 339 } 340 if (params.isEnabled() == false) { 341 System.out.println("Skipping disabled test " + params); 342 continue; 343 } 344 try { 345 runTest(params); 346 System.out.println("Passed " + params); 347 } catch (Exception e) { 348 cipherTest.setFailed(); 349 System.out.println("** Failed " + params + "**"); 350 e.printStackTrace(); 351 } 352 } 353 } 354 355 abstract void runTest(TestParameters params) throws Exception; 356 357 void sendRequest(InputStream in, OutputStream out) throws IOException { 358 out.write("GET / HTTP/1.0\r\n\r\n".getBytes()); 359 out.flush(); 360 StringBuilder sb = new StringBuilder(); 361 while (true) { 362 int ch = in.read(); 363 if (ch < 0) { 364 break; 365 } 366 sb.append((char)ch); 367 } 368 String response = sb.toString(); 369 if (response.startsWith("HTTP/1.0 200 ") == false) { 370 throw new IOException("Invalid response: " + response); 371 } 372 } 373 374 } 375 376 // for some reason, ${test.src} has a different value when the 377 // test is called from the script and when it is called directly... 378 static String pathToStores = "."; 379 static String pathToStoresSH = "."; 380 static String keyStoreFile = "keystore"; 381 static String trustStoreFile = "truststore"; 382 static char[] passwd = "passphrase".toCharArray(); 383 384 static File PATH; 385 386 private static KeyStore readKeyStore(String name) throws Exception { 387 File file = new File(PATH, name); 388 InputStream in = new FileInputStream(file); 389 KeyStore ks = KeyStore.getInstance("JKS"); 390 ks.load(in, passwd); 391 in.close(); 392 return ks; 393 } 394 395 public static void main(PeerFactory peerFactory, KeyStore keyStore, 396 String[] args) throws Exception { 397 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 CipherTest.keyStore = keyStore; 413// keyStore = readKeyStore(keyStoreFile); 414 KeyManagerFactory keyFactory = 415 KeyManagerFactory.getInstance( 416 KeyManagerFactory.getDefaultAlgorithm()); 417 keyFactory.init(keyStore, "test12".toCharArray()); 418 keyManager = (X509ExtendedKeyManager)keyFactory.getKeyManagers()[0]; 419 420 TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 421 tmf.init(keyStore); 422 trustManager = (X509TrustManager)tmf.getTrustManagers()[0]; 423 424// trustManager = new AlwaysTrustManager(); 425 SSLContext context = SSLContext.getInstance("TLS"); 426 context.init(new KeyManager[] {keyManager}, new TrustManager[] {trustManager}, null); 427 SSLContext.setDefault(context); 428 429 CipherTest cipherTest = new CipherTest(peerFactory); 430 Thread serverThread = new Thread(peerFactory.newServer(cipherTest), 431 "Server"); 432 serverThread.setDaemon(true); 433 serverThread.start(); 434 System.out.println("Done"); 435 cipherTest.run(); 436 time = System.currentTimeMillis() - time; 437 System.out.println("Done. (" + time + " ms)"); 438 } 439 440 static abstract class PeerFactory { 441 442 abstract String getName(); 443 444 abstract Client newClient(CipherTest cipherTest) throws Exception; 445 446 abstract Server newServer(CipherTest cipherTest) throws Exception; 447 448 boolean isSupported(String cipherSuite, String protocol) { 449 // skip kerberos cipher suites 450 if (cipherSuite.startsWith("TLS_KRB5")) { 451 System.out.println("Skipping unsupported test for " + 452 cipherSuite + " of " + protocol); 453 return false; 454 } 455 456 // skip SSLv2Hello protocol 457 if (protocol.equals("SSLv2Hello")) { 458 System.out.println("Skipping unsupported test for " + 459 cipherSuite + " of " + protocol); 460 return false; 461 } 462 463 // ignore exportable cipher suite for TLSv1.1 464 if (protocol.equals("TLSv1.1")) { 465 if (cipherSuite.indexOf("_EXPORT_WITH") != -1) { 466 System.out.println("Skipping obsoleted test for " + 467 cipherSuite + " of " + protocol); 468 return false; 469 } 470 } 471 472 return true; 473 } 474 } 475 476} 477 478// we currently don't do any chain verification. we assume that works ok 479// and we can speed up the test. we could also just add a plain certificate 480// chain comparision with our trusted certificates. 481class AlwaysTrustManager implements X509TrustManager { 482 483 public AlwaysTrustManager() { 484 485 } 486 487 public void checkClientTrusted(X509Certificate[] chain, String authType) 488 throws CertificateException { 489 // empty 490 } 491 492 public void checkServerTrusted(X509Certificate[] chain, String authType) 493 throws CertificateException { 494 // empty 495 } 496 497 public X509Certificate[] getAcceptedIssuers() { 498 return new X509Certificate[0]; 499 } 500} 501 502class MyX509KeyManager extends X509ExtendedKeyManager { 503 504 private final X509ExtendedKeyManager keyManager; 505 private String authType; 506 507 MyX509KeyManager(X509ExtendedKeyManager keyManager) { 508 this.keyManager = keyManager; 509 } 510 511 void setAuthType(String authType) { 512 this.authType = authType; 513 } 514 515 public String[] getClientAliases(String keyType, Principal[] issuers) { 516 if (authType == null) { 517 return null; 518 } 519 return keyManager.getClientAliases(authType, issuers); 520 } 521 522 public String chooseClientAlias(String[] keyType, Principal[] issuers, 523 Socket socket) { 524 if (authType == null) { 525 return null; 526 } 527 return keyManager.chooseClientAlias(new String[] {authType}, 528 issuers, socket); 529 } 530 531 public String chooseEngineClientAlias(String[] keyType, 532 Principal[] issuers, SSLEngine engine) { 533 if (authType == null) { 534 return null; 535 } 536 return keyManager.chooseEngineClientAlias(new String[] {authType}, 537 issuers, engine); 538 } 539 540 public String[] getServerAliases(String keyType, Principal[] issuers) { 541 throw new UnsupportedOperationException("Servers not supported"); 542 } 543 544 public String chooseServerAlias(String keyType, Principal[] issuers, 545 Socket socket) { 546 throw new UnsupportedOperationException("Servers not supported"); 547 } 548 549 public String chooseEngineServerAlias(String keyType, Principal[] issuers, 550 SSLEngine engine) { 551 throw new UnsupportedOperationException("Servers not supported"); 552 } 553 554 public X509Certificate[] getCertificateChain(String alias) { 555 return keyManager.getCertificateChain(alias); 556 } 557 558 public PrivateKey getPrivateKey(String alias) { 559 return keyManager.getPrivateKey(alias); 560 } 561 562} 563 564class DaemonThreadFactory implements ThreadFactory { 565 566 final static ThreadFactory INSTANCE = new DaemonThreadFactory(); 567 568 private final static ThreadFactory DEFAULT = Executors.defaultThreadFactory(); 569 570 public Thread newThread(Runnable r) { 571 Thread t = DEFAULT.newThread(r); 572 t.setDaemon(true); 573 return t; 574 } 575 576} 577