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(); } } } }
signature.asc
Description: OpenPGP digital signature