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