All,

As part of my testing for the recent changes to Tomcat and tcnative, I
wanted to use something like "sslscan", but that tool does not support
anything above TLSv1.

One can use OpenSSL s_client but that's fairly tedious.

I took the opportunity to write a Java client that mimics the important
parts of sslscan's capabilities.

I'm posting it in case anyone wants to use it (or improve it). It's also
a good (I think) example of how to do a long of SSL-related stuff in Java.

I had originally written this using HttpsURLConnection but there are
some things you can't get from an HttpsURLConnection (like which
protocol was actually used when a connection is made) so I switched to
using more basic SSL sockets.

If you want to use this code to build something that works with
HttpsURLConnection, all you have to do is call
SSLUtils.getSSLSocketFactory() and pass the result to
HttpsURLConnection.setDefaultSSLSocketFactory().

Enjoy,
-chris
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

/**
 * A driver class to test a server's SSL/TLS support.
 * 
 * Usage: java SSLTest [opts] host[:port]
 * 
 * Try "java SSLTest -h" for help.
 * 
 * This tester will attempts to handshake with the target host with all
 * available protocols and ciphers and report which ones were accepted and
 * which were rejected. An HTTP connection is never fully made, so these
 * connections should not flood the host's access log with entries.
 * 
 * @author Christopher Schultz
 */
public class SSLTest
{
    public static void usage()
    {
        System.out.println("Usage: java " + SSLTest.class + " [opts] 
host[:port]");
        System.out.println();
        System.out.println("-sslprotocol                 Sets the SSL/TLS 
protocol to be used (e.g. SSL, TLS, SSLv3, TLSv1.2, etc.)");
        System.out.println("-enabledprotocols protocols  Sets individual 
SSL/TLS ptotocols that should be enabled");
        System.out.println("-ciphers cipherspec          A comma-separated list 
of SSL/TLS ciphers");

        System.out.println("-truststore                  Sets the trust store 
for connections");
        System.out.println("-truststoretype type         Sets the type for the 
trust store");
        System.out.println("-truststorepassword pass     Sets the password for 
the trust store");
        System.out.println("-truststorealgorithm alg     Sets the algorithm for 
the trust store");
        System.out.println("-truststoreprovider provider Sets the crypto 
provider for the trust store");

        System.out.println("-no-check-certificate        Ignores certificate 
errors");
        System.out.println("-no-verify-hostname          Ignores hostname 
mismatches");

        System.out.println("-h -help --help     Shows this help message");
    }

    public static void main(String[] args)
        throws Exception
    {
        int connectTimeout = 0; // default = infinite
        int readTimeout = 1000;

        boolean disableHostnameVerification = true;
        boolean disableCertificateChecking = true;

        String trustStoreFilename = 
System.getProperty("javax.net.ssl.trustStore");
        String trustStorePassword = 
System.getProperty("javax.net.ssl.trustStorePassword");
        String trustStoreType = 
System.getProperty("javax.net.ssl.trustStoreType");
        String trustStoreProvider = 
System.getProperty("javax.net.ssl.trustStoreProvider");
        String trustStoreAlgorithm = null;
        String sslProtocol = "TLS";
        String[] sslEnabledProtocols = new String[] { "TLSv1", "TLSv1.1", 
"TLSv1.2" };
        String[] sslCipherSuites = null; // Default = default for protocol
        String crlFilename = null;

        if(args.length < 1)
        {
            usage();
            System.exit(0);
        }

        int argIndex;
        for(argIndex = 0; argIndex < args.length; ++argIndex)
        {
            String arg = args[argIndex];

            if(!arg.startsWith("-"))
                break;
            else if("--".equals(arg))
                break;
            else if("-no-check-certificate".equals(arg))
                disableCertificateChecking = true;
            else if("-no-verify-hostname".equals(arg))
                disableHostnameVerification = true;
            else if("-sslprotocol".equals(arg))
                sslProtocol = args[++argIndex];
            else if("-enabledprotocols".equals(arg))
                sslEnabledProtocols = args[++argIndex].split("\\s*,\\s*");
            else if("-ciphers".equals(arg))
                sslCipherSuites = args[++argIndex].split("\\s*,\\s*");
            else if("-connecttimeout".equals(arg))
                connectTimeout = Integer.parseInt(args[++argIndex]);
            else if("-readtimeout".equals(arg))
                readTimeout = Integer.parseInt(args[++argIndex]);
            else if("-truststore".equals(arg))
                trustStoreFilename = args[++argIndex];
            else if("-truststoretype".equals(arg))
                trustStoreType = args[++argIndex];
            else if("-truststorepassword".equals(arg))
                trustStorePassword = args[++argIndex];
            else if("-truststoreprovider".equals(arg))
                trustStoreProvider = args[++argIndex];
            else if("-truststorealgorithm".equals(arg))
                trustStoreAlgorithm = args[++argIndex];
            else if("--help".equals(arg)
                    || "-h".equals(arg)
                    || "-help".equals(arg))
            {
                usage();
                System.exit(0);
            }
            else
            {
                System.err.println("Unrecognized option: " + arg);
                System.exit(1);
            }
        }
        
        if(argIndex >= args.length)
        {
            System.err.println("Unexpected additional arguments: "
                               + 
java.util.Arrays.asList(args).subList(argIndex, args.length));

            usage();
            System.exit(1);
        }

        if(disableHostnameVerification)
            SSLUtils.disableSSLHostnameVerification();

        TrustManager[] trustManagers;
        if(disableCertificateChecking
           || 
"true".equalsIgnoreCase(System.getProperty("disable.ssl.cert.checks")))
        {
            trustManagers = SSLUtils.getTrustAllCertsTrustManagers();
        }
        else if(null != trustStoreFilename)
        {
            if(null == trustStoreType)
                trustStoreType = "JKS";

            trustManagers = SSLUtils.getTrustManagers(trustStoreFilename, 
trustStorePassword, trustStoreType, trustStoreProvider, trustStoreAlgorithm, 
null, crlFilename);            
        }
        else
            trustManagers = null;

        int port = 443;
        String host = args[argIndex];

        int pos = host.indexOf(':');
        if(pos > 0)
        {
            port = Integer.parseInt(host.substring(pos + 1));
            host = host.substring(0, pos);
        }
        
        System.out.println("Testing server " + host + ":" + port);

        SecureRandom rand = new SecureRandom();

        String reportFormat = "%9s %8s %s\n";
        System.out.print(String.format(reportFormat, "Supported", "Protocol", 
"Cipher"));

        InetSocketAddress address = new InetSocketAddress(host, port);

        for(String protocol : new String[] { "SSLv2", "SSLv3", "TLSv1", 
"TLSv1.1", "TLSv1.2" })
        {
            SSLContext sc;
            try
            {
                sc = SSLContext.getInstance(protocol);
            }
            catch (NoSuchAlgorithmException nsae)
            {
                System.out.print(String.format(reportFormat, "-----", protocol, 
" Not supported by client"));
                continue;
            }
            catch (Exception e)
            {
                e.printStackTrace();
                continue; // Skip this protocol
            }

            sc.init(null, null, rand);

            for(String cipherSuite : 
sc.getSocketFactory().getSupportedCipherSuites())
            {
                String status;

                SSLSocketFactory sf = SSLUtils.getSSLSocketFactory(protocol,
                                                                   new String[] 
{ protocol },
                                                                   new String[] 
{ cipherSuite },
                                                                   rand,
                                                                   
trustManagers);

                Socket sock = null;

                try
                {
                    //
                    // Note: SSLSocketFactory has several create() methods.
                    // Those that take arguments all connect immediately
                    // and have no options for specifying a connection timeout.
                    //
                    // So, we have to create a socket and connect it (with a
                    // connection timeout), then have the SSLSocketFactory wrap
                    // the already-connected socket.
                    //
                    sock = new Socket();
                    sock.setSoTimeout(readTimeout);
                    sock.connect(address, connectTimeout);

                    // Wrap plain socket in an SSL socket
                    SSLSocket socket = (SSLSocket)sf.createSocket(sock, host, 
port, true);
                    socket.startHandshake();

                    assert protocol.equals(socket.getSession().getProtocol());
                    assert 
cipherSuite.equals(socket.getSession().getCipherSuite());

                    status = "Accepted";
                }
                catch (SocketTimeoutException ste)
                {
                    status = "Failed";
                }
                catch (IOException ioe)
                {
                    // System.out.println(ioe);
                    status = "Rejected";
                }
                catch (Exception e)
                {
                    System.out.print(e.getMessage());
                    status = "Rejected";
                }
                finally
                {
                    if(null != sock) try { sock.close(); }
                    catch (IOException ioe) { ioe.printStackTrace(); }
                }
                System.out.print(String.format(reportFormat,
                                               status,
                                               protocol,
                                               cipherSuite));

            }
        }

        // Now get generic and allow the server to decide on the protocol and 
cipher suite
        SSLSocketFactory sf = SSLUtils.getSSLSocketFactory(sslProtocol,
                                                           sslEnabledProtocols,
                                                           sslCipherSuites,
                                                           rand,
                                                           trustManagers);

        Socket sock = null;

        try
        {
            //
            // Note: SSLSocketFactory has several create() methods.
            // Those that take arguments all connect immediately
            // and have no options for specifying a connection timeout.
            //
            // So, we have to create a socket and connect it (with a
            // connection timeout), then have the SSLSocketFactory wrap
            // the already-connected socket.
            //
            sock = new Socket();
            sock.connect(address, connectTimeout);
            sock.setSoTimeout(readTimeout);

            // Wrap plain socket in an SSL socket
            SSLSocket socket = (SSLSocket)sf.createSocket(sock, host, port, 
true);
            socket.startHandshake();

            System.out.print("Given this client's capabilities, the server 
prefers protocol=");
            System.out.print(socket.getSession().getProtocol());
            System.out.print(", cipher=");
            System.out.println(socket.getSession().getCipherSuite());
        }
        finally
        {
            if (null != sock) try { sock.close(); }
            catch (IOException ioe) { ioe.printStackTrace(); }
        }
    }
}
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.cert.CRL;
import java.security.cert.CRLException;
import java.security.cert.CertPathParameters;
import java.security.cert.CertStore;
import java.security.cert.CertStoreParameters;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.Collection;

import javax.net.ssl.CertPathTrustManagerParameters;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.ManagerFactoryParameters;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

/**
 * Lots of useful SSL-related goodies.
 *
 * @author Christopher Schultz
 * @author Apache Software Foundation (some code adapted/lifted from Apache 
Tomcat).
 */
public class SSLUtils
{
    public static void disableSSLHostnameVerification()
    {
        HostnameVerifier verifyEverything = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session)
            {
                return true;
            }
        };

        HttpsURLConnection.setDefaultHostnameVerifier(verifyEverything);
    }

    private static final TrustManager[] trustAllCerts = new TrustManager[] {
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
                public void checkClientTrusted(X509Certificate[] certs,
                                               String authType) {
                    // Trust all clients
                }
                public void checkServerTrusted(X509Certificate[] certs,
                                               String authType) {
                    // Trust all servers
                }
            }
        };

    public static TrustManager[] getTrustAllCertsTrustManagers()
    {
        return trustAllCerts.clone();
    }
    
    /**
     * Configures SSLSocketFactory for Java's HttpsURLConnection.
     */
    public static void configureHttpsURLConnection(String protocol,
                                                   String[] sslEnabledProtocols,
                                                   String[] sslCipherSuites,
                                                   SecureRandom random,
                                                   TrustManager[] tms)
        throws NoSuchAlgorithmException, KeyManagementException
    {
        
HttpsURLConnection.setDefaultSSLSocketFactory(getSSLSocketFactory(protocol,
                                                                          
sslEnabledProtocols,
                                                                          
sslCipherSuites,
                                                                          
random,
                                                                          tms));
    }
    
    /**
     * Creates an SSLSocketFactory that supports only the specified protocols
     * and ciphers.
     */
    public static SSLSocketFactory getSSLSocketFactory(String protocol,
                                                       String[] 
sslEnabledProtocols,
                                                       String[] sslCipherSuites,
                                                       SecureRandom random,
                                                       TrustManager[] tms)
        throws NoSuchAlgorithmException, KeyManagementException
    {
        SSLContext sc = SSLContext.getInstance(protocol);

        sc.init(null, tms, random);

        SSLSocketFactory sf = sc.getSocketFactory();

        if(null != sslEnabledProtocols
           || null != sslCipherSuites)
            sf = new CustomSSLSocketFactory(sf,
                                            sslEnabledProtocols,
                                            sslCipherSuites);

        return sf;
    }

    /**
     * In order to customize the specific enabled protocols and cipher suites,
     * a customized SSLSocketFactory must be used.
     * 
     * This is just a wrapper around that customization.
     */
    public static class CustomSSLSocketFactory
        extends javax.net.ssl.SSLSocketFactory
    {
        private final String[] _sslEnabledProtocols;
        private final String[] _sslCipherSuites;
        private final SSLSocketFactory _base;

        public CustomSSLSocketFactory(SSLSocketFactory base,
                                  String[] sslEnabledProtocols,
                                  String[] sslCipherSuites)
        {
            _base = base;
            if(null == sslEnabledProtocols)
                _sslEnabledProtocols = null;
            else
                _sslEnabledProtocols = sslEnabledProtocols.clone();
            if(null == sslCipherSuites || 0 == sslCipherSuites.length)
                _sslCipherSuites = getDefaultCipherSuites();
            else if(1 == sslCipherSuites.length && 
"ALL".equalsIgnoreCase(sslCipherSuites[0]))
                _sslCipherSuites = getSupportedCipherSuites();
            else
                _sslCipherSuites = sslCipherSuites.clone();
        }

        public String[] getDefaultCipherSuites() {
            return _base.getDefaultCipherSuites();
        }
        public String[] getSupportedCipherSuites() {
            return _base.getSupportedCipherSuites();
        }
        
        private SSLSocket customize(Socket s)
        {
            SSLSocket socket = (SSLSocket)s;

            if(null != _sslEnabledProtocols)
                socket.setEnabledProtocols(_sslEnabledProtocols);

            socket.setEnabledCipherSuites(_sslCipherSuites);

            return socket;
        }
        
        @Override
        public Socket createSocket(Socket s,
                                   String host,
                                   int port,
                                   boolean autoClose)
            throws IOException
        {
            return customize(_base.createSocket(s, host, port, autoClose));
        }
        @Override
        public Socket createSocket(String host, int port)
            throws IOException, UnknownHostException
        {
            return customize(_base.createSocket(host, port));
        }
        @Override
        public Socket createSocket(InetAddress host, int port)
            throws IOException
        {
            return customize(_base.createSocket(host, port));
        }
        @Override
        public Socket createSocket(String host, int port,
                                   InetAddress localHost, int localPort)
            throws IOException, UnknownHostException
        {
            return customize(_base.createSocket(host, port, localHost, 
localPort));
        }
        @Override
        public Socket createSocket(InetAddress address, int port,
                                   InetAddress localAddress, int localPort)
            throws IOException
        {
            return customize(_base.createSocket(address, port, localAddress, 
localPort));
        }
    }

    //
    // All the code for loading TrustManagers was adapted from code in
    // the Apache Tomcat project.
    //

    /**
     * Gets an array of TrustManagers for the specified trust store
     * and optional CRL file.
     *
     * @param trustStoreFilename
     * @param trustStorePassword
     * @param trustStoreType
     * @param trustStoreProvider
     * @param trustStoreAlgorithm
     * @param maxCertificatePathLength
     * @param crlFilename
     *
     * @return An array of TrustManagers
     *
     * @throws IOException
     * @throws KeyStoreException
     * @throws NoSuchProviderException
     * @throws NoSuchAlgorithmException
     * @throws CertificateException
     * @throws InvalidAlgorithmParameterException
     * @throws CRLException
     */
    protected static TrustManager[] getTrustManagers(String trustStoreFilename,
                                                     String trustStorePassword,
                                                     String trustStoreType,
                                                     String trustStoreProvider,
                                                     String trustStoreAlgorithm,
                                                     Integer 
maxCertificatePathLength,
                                                     String crlFilename)
        throws IOException, KeyStoreException, NoSuchProviderException, 
NoSuchAlgorithmException, CertificateException, 
InvalidAlgorithmParameterException, CRLException
    {
        KeyStore trustStore = getStore(trustStoreFilename,
                                       trustStorePassword,
                                       trustStoreType,
                                       trustStoreProvider);

        if(null == trustStoreAlgorithm)
            trustStoreAlgorithm = TrustManagerFactory.getDefaultAlgorithm();

        TrustManagerFactory tmf =
                TrustManagerFactory.getInstance(trustStoreAlgorithm);
        if (null == crlFilename)
        {
            tmf.init(trustStore);
        }
        else
        {
            CertPathParameters params =
                getParameters(trustStoreAlgorithm,
                              crlFilename,
                              maxCertificatePathLength,
                              trustStore);

            ManagerFactoryParameters mfp =
                new CertPathTrustManagerParameters(params);

            tmf.init(mfp);
        }

        return tmf.getTrustManagers();
    }

    /**
     * Return the initialization parameters for the TrustManager.
     * Currently, only the default <code>PKIX</code> is supported.
     *
     * @param algorithm The algorithm to get parameters for.
     * @param crlFilename The path to the CRL file.
     * @param maxCertificateChainLength Optional maximum cert chain length.
     * @param trustStore The configured TrustStore.
     *
     * @return The parameters including the TrustStore and any CRLs.
     *
     * @throws InvalidAlgorithmParameterException
     * @throws KeyStoreException
     * @throws IOException
     * @throws CertificateException
     * @throws CRLException
     * @throws NoSuchAlgorithmException
     */
    protected static CertPathParameters getParameters(String algorithm,
                                                      String crlFilename,
                                                      Integer 
maxCertificateChainLength,
                                                      KeyStore trustStore)
        throws KeyStoreException, InvalidAlgorithmParameterException, 
CRLException, CertificateException, IOException, NoSuchAlgorithmException
    {
        CertPathParameters params = null;
        if("PKIX".equalsIgnoreCase(algorithm)) {
            PKIXBuilderParameters xparams =
                new PKIXBuilderParameters(trustStore, new X509CertSelector());
            Collection<? extends CRL> crls = getCRLs(crlFilename);
            CertStoreParameters csp = new CollectionCertStoreParameters(crls);
            CertStore store = CertStore.getInstance("Collection", csp);
            xparams.addCertStore(store);
            xparams.setRevocationEnabled(true);

            if(maxCertificateChainLength != null)
                xparams.setMaxPathLength(maxCertificateChainLength.intValue());

            params = xparams;
        } else {
            throw new CRLException("CRLs not supported for type: " + algorithm);
        }
        return params;
    }

    /**
     * Loads a collection of Certificate Revocation Lists (CRLs)
     * from a file.
     *
     * @param crlFilename The file name of the CRL.
     *
     * @return A Collection of CRLs from the specified file.
     *
     * @throws IOException If the CRL file could not be loaded.
     * @throws CRLException If the CRL list cannot be loaded.
     * @throws CertificateException If there is a problem with one
     *         of the certificates in the revocation list.
     */
    public static Collection<? extends CRL> getCRLs(String crlFilename)
        throws IOException, CRLException, CertificateException
    {
        File crlFile = new File(crlFilename);

        Collection<? extends CRL> crls = null;
        InputStream is = null;
        try
        {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            is = new FileInputStream(crlFile);
            crls = cf.generateCRLs(is);
        }
        finally
        {
            if(is != null) try{ is.close(); }
            catch(IOException ioe) { ioe.printStackTrace(); }
        }
        return crls;
    }

    /**
     * Loads a keystore.
     *
     * @param storeFilename The file name of the keystore.
     * @param storePassword The keystore password.
     * @param storeType The type of the keystore.
     * @param storeProvider Optional keystore provider.
     *
     * @return A KeyStore loaded from the specified file.
     *
     * @throws IOException If the file cannot be read.
     * @throws KeyStoreException If the KeyStore cannot be read.
     * @throws NoSuchProviderException If the provider is not recognized.
     * @throws NoSuchAlgorithmException If the an algorithm used by the 
KeyStore is no recognized.
     * @throws CertificateException If there is a problem with a certificate in 
the KeyStore.
     */
    public static KeyStore getStore(String storeFilename,
                                    String storePassword,
                                    String storeType,
                                    String storeProvider)
        throws IOException, KeyStoreException, NoSuchProviderException, 
CertificateException, NoSuchAlgorithmException
    {
        KeyStore ks = null;
        InputStream in = null;

        try
        {
            if(null == storeProvider)
                ks = KeyStore.getInstance(storeType);
            else
                ks = KeyStore.getInstance(storeType, storeProvider);

            // TODO: Explicitly check for PKCS11?

            in = new FileInputStream(storeFilename);

            char[] storePass = null;
            if (storePassword != null && !"".equals(storePassword))
                storePass = storePassword.toCharArray();

            ks.load(in, storePass);

            return ks;
        }
        finally
        {
            if(null != in) try { in.close(); }
            catch (IOException ioe) { ioe.printStackTrace(); }
        }
    }
}

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to