diff -r 4ebc2e2fb97c -r 71c04702a3d5 test/jdk/javax/net/ssl/sanity/interop/CipherTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/javax/net/ssl/sanity/interop/CipherTest.java Tue Sep 12 19:03:39 2017 +0200 @@ -0,0 +1,591 @@ +/* + * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.concurrent.*; + +import java.security.*; +import java.security.cert.*; +import java.security.cert.Certificate; + +import javax.net.ssl.*; + +/** + * Test that all ciphersuites work in all versions and all client + * authentication types. The way this is setup the server is stateless and + * all checking is done on the client side. + * + * The test is multithreaded to speed it up, especially on multiprocessor + * machines. To simplify debugging, run with -DnumThreads=1. + * + * @author Andreas Sterbenz + */ +public class CipherTest { + + // use any available port for the server socket + static int serverPort = 0; + + final int THREADS; + + // assume that if we do not read anything for 20 seconds, something + // has gone wrong + final static int TIMEOUT = 20 * 1000; + + static KeyStore trustStore, keyStore; + static X509ExtendedKeyManager keyManager; + static X509TrustManager trustManager; + static SecureRandom secureRandom; + + private static PeerFactory peerFactory; + + static abstract class Server implements Runnable { + + final CipherTest cipherTest; + + Server(CipherTest cipherTest) throws Exception { + this.cipherTest = cipherTest; + } + + public abstract void run(); + + void handleRequest(InputStream in, OutputStream out) throws IOException { + boolean newline = false; + StringBuilder sb = new StringBuilder(); + while (true) { + int ch = in.read(); + if (ch < 0) { + throw new EOFException(); + } + sb.append((char)ch); + if (ch == '\r') { + // empty + } else if (ch == '\n') { + if (newline) { + // 2nd newline in a row, end of request + break; + } + newline = true; + } else { + newline = false; + } + } + String request = sb.toString(); + if (request.startsWith("GET / HTTP/1.") == false) { + throw new IOException("Invalid request: " + request); + } + out.write("HTTP/1.0 200 OK\r\n\r\n".getBytes()); + } + + } + + public static class TestParameters { + + String cipherSuite; + String protocol; + String clientAuth; + + TestParameters(String cipherSuite, String protocol, + String clientAuth) { + this.cipherSuite = cipherSuite; + this.protocol = protocol; + this.clientAuth = clientAuth; + } + + boolean isEnabled() { + return TLSCipherStatus.isEnabled(cipherSuite, protocol); + } + + public String toString() { + String s = cipherSuite + " in " + protocol + " mode"; + if (clientAuth != null) { + s += " with " + clientAuth + " client authentication"; + } + return s; + } + + static enum TLSCipherStatus { + // cipher suites supported since TLS 1.2 + CS_01("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", 0x0303, 0xFFFF), + CS_02("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", 0x0303, 0xFFFF), + CS_03("TLS_RSA_WITH_AES_256_CBC_SHA256", 0x0303, 0xFFFF), + CS_04("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", 0x0303, 0xFFFF), + CS_05("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", 0x0303, 0xFFFF), + CS_06("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", 0x0303, 0xFFFF), + CS_07("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", 0x0303, 0xFFFF), + + CS_08("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF), + CS_09("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF), + CS_10("TLS_RSA_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF), + CS_11("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF), + CS_12("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF), + CS_13("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF), + CS_14("TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF), + + CS_15("TLS_DH_anon_WITH_AES_256_CBC_SHA256", 0x0303, 0xFFFF), + CS_16("TLS_DH_anon_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF), + CS_17("TLS_RSA_WITH_NULL_SHA256", 0x0303, 0xFFFF), + + CS_20("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", 0x0303, 0xFFFF), + CS_21("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 0x0303, 0xFFFF), + CS_22("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", 0x0303, 0xFFFF), + CS_23("TLS_RSA_WITH_AES_256_GCM_SHA384", 0x0303, 0xFFFF), + CS_24("TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", 0x0303, 0xFFFF), + CS_25("TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", 0x0303, 0xFFFF), + CS_26("TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", 0x0303, 0xFFFF), + CS_27("TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", 0x0303, 0xFFFF), + + CS_28("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 0x0303, 0xFFFF), + CS_29("TLS_RSA_WITH_AES_128_GCM_SHA256", 0x0303, 0xFFFF), + CS_30("TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", 0x0303, 0xFFFF), + CS_31("TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", 0x0303, 0xFFFF), + CS_32("TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", 0x0303, 0xFFFF), + CS_33("TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", 0x0303, 0xFFFF), + + CS_34("TLS_DH_anon_WITH_AES_256_GCM_SHA384", 0x0303, 0xFFFF), + CS_35("TLS_DH_anon_WITH_AES_128_GCM_SHA256", 0x0303, 0xFFFF), + + // cipher suites obsoleted since TLS 1.2 + CS_50("SSL_RSA_WITH_DES_CBC_SHA", 0x0000, 0x0303), + CS_51("SSL_DHE_RSA_WITH_DES_CBC_SHA", 0x0000, 0x0303), + CS_52("SSL_DHE_DSS_WITH_DES_CBC_SHA", 0x0000, 0x0303), + CS_53("SSL_DH_anon_WITH_DES_CBC_SHA", 0x0000, 0x0303), + CS_54("TLS_KRB5_WITH_DES_CBC_SHA", 0x0000, 0x0303), + CS_55("TLS_KRB5_WITH_DES_CBC_MD5", 0x0000, 0x0303), + + // cipher suites obsoleted since TLS 1.1 + CS_60("SSL_RSA_EXPORT_WITH_RC4_40_MD5", 0x0000, 0x0302), + CS_61("SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", 0x0000, 0x0302), + CS_62("SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0000, 0x0302), + CS_63("SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0000, 0x0302), + CS_64("SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", 0x0000, 0x0302), + CS_65("SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA", 0x0000, 0x0302), + CS_66("TLS_KRB5_EXPORT_WITH_RC4_40_SHA", 0x0000, 0x0302), + CS_67("TLS_KRB5_EXPORT_WITH_RC4_40_MD5", 0x0000, 0x0302), + CS_68("TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", 0x0000, 0x0302), + CS_69("TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", 0x0000, 0x0302), + + // ignore TLS_EMPTY_RENEGOTIATION_INFO_SCSV always + CS_99("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", 0xFFFF, 0x0000); + + // the cipher suite name + final String cipherSuite; + + // supported since protocol version + final int supportedSince; + + // obsoleted since protocol version + final int obsoletedSince; + + TLSCipherStatus(String cipherSuite, + int supportedSince, int obsoletedSince) { + this.cipherSuite = cipherSuite; + this.supportedSince = supportedSince; + this.obsoletedSince = obsoletedSince; + } + + static boolean isEnabled(String cipherSuite, String protocol) { + int versionNumber = toVersionNumber(protocol); + + if (versionNumber < 0) { + return true; // unlikely to happen + } + + for (TLSCipherStatus status : TLSCipherStatus.values()) { + if (cipherSuite.equals(status.cipherSuite)) { + if ((versionNumber < status.supportedSince) || + (versionNumber >= status.obsoletedSince)) { + return false; + } + + return true; + } + } + + return true; + } + + private static int toVersionNumber(String protocol) { + int versionNumber = -1; + + switch (protocol) { + case "SSLv2Hello": + versionNumber = 0x0002; + break; + case "SSLv3": + versionNumber = 0x0300; + break; + case "TLSv1": + versionNumber = 0x0301; + break; + case "TLSv1.1": + versionNumber = 0x0302; + break; + case "TLSv1.2": + versionNumber = 0x0303; + break; + default: + // unlikely to happen + } + + return versionNumber; + } + } + } + + private List tests; + private Iterator testIterator; + private SSLSocketFactory factory; + private boolean failed; + + private CipherTest(PeerFactory peerFactory) throws IOException { + THREADS = Integer.parseInt(System.getProperty("numThreads", "4")); + factory = (SSLSocketFactory)SSLSocketFactory.getDefault(); + SSLSocket socket = (SSLSocket)factory.createSocket(); + String[] cipherSuites = socket.getSupportedCipherSuites(); + String[] protocols = socket.getSupportedProtocols(); + String[] clientAuths = {null, "RSA", "DSA"}; + tests = new ArrayList( + cipherSuites.length * protocols.length * clientAuths.length); + for (int i = 0; i < cipherSuites.length; i++) { + String cipherSuite = cipherSuites[i]; + + for (int j = 0; j < protocols.length; j++) { + String protocol = protocols[j]; + + if (!peerFactory.isSupported(cipherSuite, protocol)) { + continue; + } + + for (int k = 0; k < clientAuths.length; k++) { + String clientAuth = clientAuths[k]; + if ((clientAuth != null) && + (cipherSuite.indexOf("DH_anon") != -1)) { + // no client with anonymous ciphersuites + continue; + } + tests.add(new TestParameters(cipherSuite, protocol, + clientAuth)); + } + } + } + testIterator = tests.iterator(); + } + + synchronized void setFailed() { + failed = true; + } + + public void run() throws Exception { + Thread[] threads = new Thread[THREADS]; + for (int i = 0; i < THREADS; i++) { + try { + threads[i] = new Thread(peerFactory.newClient(this), + "Client " + i); + } catch (Exception e) { + e.printStackTrace(); + return; + } + threads[i].start(); + } + try { + for (int i = 0; i < THREADS; i++) { + threads[i].join(); + } + } catch (InterruptedException e) { + setFailed(); + e.printStackTrace(); + } + if (failed) { + throw new Exception("*** Test '" + peerFactory.getName() + + "' failed ***"); + } else { + System.out.println("Test '" + peerFactory.getName() + + "' completed successfully"); + } + } + + synchronized TestParameters getTest() { + if (failed) { + return null; + } + if (testIterator.hasNext()) { + return (TestParameters)testIterator.next(); + } + return null; + } + + SSLSocketFactory getFactory() { + return factory; + } + + static abstract class Client implements Runnable { + + final CipherTest cipherTest; + + Client(CipherTest cipherTest) throws Exception { + this.cipherTest = cipherTest; + } + + public final void run() { + while (true) { + TestParameters params = cipherTest.getTest(); + if (params == null) { + // no more tests + break; + } + if (params.isEnabled() == false) { + System.out.println("Skipping disabled test " + params); + continue; + } + try { + runTest(params); + System.out.println("Passed " + params); + } catch (Exception e) { + cipherTest.setFailed(); + System.out.println("** Failed " + params + "**"); + e.printStackTrace(); + } + } + } + + abstract void runTest(TestParameters params) throws Exception; + + void sendRequest(InputStream in, OutputStream out) throws IOException { + out.write("GET / HTTP/1.0\r\n\r\n".getBytes()); + out.flush(); + StringBuilder sb = new StringBuilder(); + while (true) { + int ch = in.read(); + if (ch < 0) { + break; + } + sb.append((char)ch); + } + String response = sb.toString(); + if (response.startsWith("HTTP/1.0 200 ") == false) { + throw new IOException("Invalid response: " + response); + } + } + + } + + // for some reason, ${test.src} has a different value when the + // test is called from the script and when it is called directly... + static String pathToStores = "../../etc"; + static String pathToStoresSH = "."; + static String keyStoreFile = "keystore"; + static String trustStoreFile = "truststore"; + static char[] passwd = "passphrase".toCharArray(); + + static File PATH; + + private static KeyStore readKeyStore(String name) throws Exception { + File file = new File(PATH, name); + InputStream in = new FileInputStream(file); + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(in, passwd); + in.close(); + return ks; + } + + public static void main(PeerFactory peerFactory, String[] args) + throws Exception { + long time = System.currentTimeMillis(); + String relPath; + if ((args != null) && (args.length > 0) && args[0].equals("sh")) { + relPath = pathToStoresSH; + } else { + relPath = pathToStores; + } + PATH = new File(System.getProperty("test.src", "."), relPath); + CipherTest.peerFactory = peerFactory; + System.out.print( + "Initializing test '" + peerFactory.getName() + "'..."); + secureRandom = new SecureRandom(); + secureRandom.nextInt(); + trustStore = readKeyStore(trustStoreFile); + keyStore = readKeyStore(keyStoreFile); + KeyManagerFactory keyFactory = + KeyManagerFactory.getInstance( + KeyManagerFactory.getDefaultAlgorithm()); + keyFactory.init(keyStore, passwd); + keyManager = (X509ExtendedKeyManager)keyFactory.getKeyManagers()[0]; + trustManager = new AlwaysTrustManager(); + + CipherTest cipherTest = new CipherTest(peerFactory); + Thread serverThread = new Thread(peerFactory.newServer(cipherTest), + "Server"); + serverThread.setDaemon(true); + serverThread.start(); + System.out.println("Done"); + cipherTest.run(); + time = System.currentTimeMillis() - time; + System.out.println("Done. (" + time + " ms)"); + } + + static abstract class PeerFactory { + + abstract String getName(); + + abstract Client newClient(CipherTest cipherTest) throws Exception; + + abstract Server newServer(CipherTest cipherTest) throws Exception; + + boolean isSupported(String cipherSuite, String protocol) { + // skip kerberos cipher suites + if (cipherSuite.startsWith("TLS_KRB5")) { + System.out.println("Skipping unsupported test for " + + cipherSuite + " of " + protocol); + return false; + } + + // skip SSLv2Hello protocol + if (protocol.equals("SSLv2Hello")) { + System.out.println("Skipping unsupported test for " + + cipherSuite + " of " + protocol); + return false; + } + + // ignore exportable cipher suite for TLSv1.1 + if (protocol.equals("TLSv1.1")) { + if (cipherSuite.indexOf("_EXPORT_WITH") != -1) { + System.out.println("Skipping obsoleted test for " + + cipherSuite + " of " + protocol); + return false; + } + } + + // ignore obsoleted cipher suite for the specified protocol + // TODO + + // ignore unsupported cipher suite for the specified protocol + // TODO + + return true; + } + } + +} + +// we currently don't do any chain verification. we assume that works ok +// and we can speed up the test. we could also just add a plain certificate +// chain comparision with our trusted certificates. +class AlwaysTrustManager implements X509TrustManager { + + public AlwaysTrustManager() { + + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + // empty + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + // empty + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } +} + +class MyX509KeyManager extends X509ExtendedKeyManager { + + private final X509ExtendedKeyManager keyManager; + private String authType; + + MyX509KeyManager(X509ExtendedKeyManager keyManager) { + this.keyManager = keyManager; + } + + void setAuthType(String authType) { + this.authType = authType; + } + + public String[] getClientAliases(String keyType, Principal[] issuers) { + if (authType == null) { + return null; + } + return keyManager.getClientAliases(authType, issuers); + } + + public String chooseClientAlias(String[] keyType, Principal[] issuers, + Socket socket) { + if (authType == null) { + return null; + } + return keyManager.chooseClientAlias(new String[] {authType}, + issuers, socket); + } + + public String chooseEngineClientAlias(String[] keyType, + Principal[] issuers, SSLEngine engine) { + if (authType == null) { + return null; + } + return keyManager.chooseEngineClientAlias(new String[] {authType}, + issuers, engine); + } + + public String[] getServerAliases(String keyType, Principal[] issuers) { + throw new UnsupportedOperationException("Servers not supported"); + } + + public String chooseServerAlias(String keyType, Principal[] issuers, + Socket socket) { + throw new UnsupportedOperationException("Servers not supported"); + } + + public String chooseEngineServerAlias(String keyType, Principal[] issuers, + SSLEngine engine) { + throw new UnsupportedOperationException("Servers not supported"); + } + + public X509Certificate[] getCertificateChain(String alias) { + return keyManager.getCertificateChain(alias); + } + + public PrivateKey getPrivateKey(String alias) { + return keyManager.getPrivateKey(alias); + } + +} + +class DaemonThreadFactory implements ThreadFactory { + + final static ThreadFactory INSTANCE = new DaemonThreadFactory(); + + private final static ThreadFactory DEFAULT = Executors.defaultThreadFactory(); + + public Thread newThread(Runnable r) { + Thread t = DEFAULT.newThread(r); + t.setDaemon(true); + return t; + } + +}