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