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