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