10

Look at the bottom of this question for more up to date information

I am trying to intercept all SSL handshakes (so that I can get information on them as well as present visual information to users, much like the green lock in browsers) that happen through my Jersey client. Unfortunately it does not seem like Jersey is using my SSLSocketFactory implementation, because none of the createSocket methods are called. No errors occur, it is just that nothing gets logged. The code should be clear:

Invocation + Instantiation:

this.httpClient = getHttpsClient(new DefaultSSLContextProvider());
Invocation.Builder invBuilder = httpClient.target(API_URL_PRIVATE + API_VERSION_2 + "markets").request(MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN, MediaType.TEXT_HTML);
invBuilder.header("Content-Type", "application/x-www-form-urlencoded");
invBuilder.header("User-Agent", USER_AGENT);

Response response = invBuilder.get();
logger.debug("response: " + response);

httpClient:

public Client getHttpsClient(SSLContextProvider sslContextProvider) throws KeyStoreException
{
    ClientConfig config = new ClientConfig().connectorProvider(new HttpUrlConnectorProvider().connectionFactory(
            url ->
            {
                HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
                connection.setSSLSocketFactory(sslContextProvider.getSSLSocketFactory());
                return connection;
            }));

    return ClientBuilder.newBuilder()
            .sslContext(sslContextProvider.getSSLContext())
            .withConfig(config)
            .build();
}

DefaultSSLContextProvider:

public class DefaultSSLContextProvider implements SSLContextProvider
{
    private SSLContext sslContext;
    private ObservableSSLSocketFactory observableSSLSocketFactory;

    private static final Logger logger = LoggerFactory.getLogger(DefaultSSLContextProvider.class);

    public DefaultSSLContextProvider()
    {
        try
        {
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
            sslContext = SSLContext.getInstance("SSL");
            KeyStore keyStore = getKeyStore();
            trustManagerFactory.init(keyStore);
            sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
            observableSSLSocketFactory = new ObservableSSLSocketFactory(sslContext);
            HttpsURLConnection.setDefaultSSLSocketFactory(observableSSLSocketFactory);
            SSLContext.setDefault(sslContext);
        }
        catch (NoSuchAlgorithmException e)
        {
            throw new RuntimeException(e);
        }
        catch (KeyManagementException | KeyStoreException e)
        {
            logger.error("could not create DefaultSSLContextProvider", e);
            throw new IllegalStateException(e);
        }
    }

    @Override
    public SSLContext getSSLContext()
    {
        return sslContext;
    }

    @Override
    public SSLSocketFactory getSSLSocketFactory()
    {
        return observableSSLSocketFactory;
    }

    @Override
    public KeyStore getKeyStore()
    {
        // snip
    }
}

ObservableSSLSocketFactory:

/**
 * Based heavily on:
 * http://stackoverflow.com/a/23365536/3634630
 */
public class ObservableSSLSocketFactory extends SSLSocketFactory
{
    private final SSLContext sslContext;
    private final String[] preferredCipherSuites;
    private final String[] preferredProtocols;

    private static final Logger logger = LoggerFactory.getLogger(ObservableSSLSocketFactory.class);

    protected ObservableSSLSocketFactory(SSLContext sslContext)
    {
        logger.debug("CREATING OBSERVABLE SOCKET FACTORY!");
        this.sslContext = sslContext;
        preferredCipherSuites = getCiphers();
        preferredProtocols = getProtocols();
        logger.debug("Observable socket factory created");
        logger.debug("preferredCipherSuites: " + preferredCipherSuites);
        logger.debug("preferredProcotols: " + preferredProtocols);
    }

    @Override
    public String[] getDefaultCipherSuites()
    {
        return preferredCipherSuites;
    }

    @Override
    public String[] getSupportedCipherSuites()
    {
        return preferredCipherSuites;
    }

    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException
    {
        logger.debug("creating ssl socket");
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(s, host, port, autoClose);

        sslSocket.addHandshakeCompletedListener(new HandshakeListener());
        sslSocket.setEnabledProtocols(preferredProtocols);
        sslSocket.setEnabledCipherSuites(preferredCipherSuites);

        return sslSocket;
    }

    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException
    {
        logger.debug("creating ssl socket");
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(address, port, localAddress, localPort);

        sslSocket.addHandshakeCompletedListener(new HandshakeListener());
        sslSocket.setEnabledProtocols(preferredProtocols);
        sslSocket.setEnabledCipherSuites(preferredCipherSuites);

        return sslSocket;
    }

    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException
    {
        logger.debug("creating ssl socket");
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        SSLSocket sslSocket = (SSLSocket)sslSocketFactory.createSocket(host, port, localHost, localPort);

        sslSocket.addHandshakeCompletedListener(new HandshakeListener());
        sslSocket.setEnabledProtocols(preferredProtocols);
        sslSocket.setEnabledCipherSuites(preferredCipherSuites);

        return sslSocket;
    }

    public Socket createSocket(InetAddress host, int port) throws IOException
    {
        logger.debug("creating ssl socket");
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        SSLSocket sslSocket = (SSLSocket)sslSocketFactory.createSocket(host, port);

        sslSocket.addHandshakeCompletedListener(new HandshakeListener());
        sslSocket.setEnabledProtocols(preferredProtocols);
        sslSocket.setEnabledCipherSuites(preferredCipherSuites);

        return sslSocket;
    }

    public Socket createSocket(String host, int port) throws IOException
    {
        logger.debug("creating ssl socket");
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        SSLSocket sslSocket = (SSLSocket)sslSocketFactory.createSocket(host, port);

        sslSocket.addHandshakeCompletedListener(new HandshakeListener());
        sslSocket.setEnabledProtocols(preferredProtocols);
        sslSocket.setEnabledCipherSuites(preferredCipherSuites);

        return sslSocket;
    }

    private String[] getProtocols()
    {
        // snip
    }

    private String[] getCiphers()
    {
        // snip
    }

    class HandshakeListener implements HandshakeCompletedListener
    {
        public HandshakeListener()
        {
            logger.debug("Created new HandshakeListener");
        }

        public void handshakeCompleted(HandshakeCompletedEvent e)
        {
            logger.debug("Handshake successful!");
            logger.debug("using cipher suite: " + e.getCipherSuite());
        }
    }

}

As I said, no exceptions or errors occur (and indeed the original request goes through with no problem (HTTP 200), however the only things that are logged are:

00:01:37.867 DEBUG [ObservableSSLSocketFactory] CREATING OBSERVABLE SOCKET FACTORY!
00:01:38.072 DEBUG [ObservableSSLSocketFactory] Observable socket factory created
00:01:38.073 DEBUG [ObservableSSLSocketFactory] preferredCipherSuites: [TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
00:01:38.073 DEBUG [ObservableSSLSocketFactory] preferredProcotols: [TLSv1, TLSv1.1, TLSv1.2]
00:01:39.435 DEBUG [Exchange] response: InboundJaxrsResponse{context=ClientResponse{method=GET, uri=https://www.bitstamp.net/api/order_book/, status=200, reason=OK}}

Nothing from createSocket()'s or the HandshakeCompletedListener.

Any help would be greatly appreciated.

Update 1: I added some additional log statements, and the situation is indeed strange. The Jersey client is in fact calling the HttpUrlConnectorProvider implementation, and in fact an instance of ObservableSSLSocketFactory is set on the connection, it appears that when the connect method is called on the HttpsURLConnection, it does not use the socket factory.

Update 2: I found an old bug which is titled: "HttpsURLConnection not using the set SSLSocketFactory for creating all its Sockets". This seems to be my exact problem. This bug was stated to be fixed sometime in JDK7. I am using (as stated) 8u60, which is quite beyond the time this bug was fixed. I am quite puzzled. I found the java argument -Djavax.net.debug=all and set it - I did not see any errors or anything out of place, it seems to be that the HttpsUrlConnection is not using the SSLSocketFactory set on it.

Update 3: Taking a suggestion from wyvern on #java @ irc.freenode.net, I decided to use the apache-connector instead of the JDK HttpsUrlConnection connector, and finally after some smudging and coaxing, I am presented with:

[HandshakeCompletedNotify-Thread][ObservableSSLConnectionSocketFactory] Handshake successful!

So, I guess I am all set then =).

brcolow
  • 1,042
  • 2
  • 11
  • 33
  • Some confusion here. There's a difference between 'default' and 'preferred', and a cipher suite is not a protocol. – user207421 Jun 30 '15 at 00:54
  • Thank you - I corrected that error. Indeed there is a difference between default and preferred, thank you for pointing that out, and I will take that into account once this proof of concept is working. The problem remains. – brcolow Jun 30 '15 at 05:56
  • Also for clarification I added the full log output. – brcolow Jun 30 '15 at 06:01
  • You aren't actually doing anything with that `Client` beyond creating it and setting its target. So, no connect, no I/O, no handshake. – user207421 Jul 01 '15 at 04:00
  • Sorry, these are simply copy and paste errors (I am copy and pasting it out of several methods that facilitate good SOLID principles, but are quite verbose as I think it would help potential answerers if I focus only on the most important details, while still being able to properly present the problem in its full context). I have added the appropriate invocation. As I stated (and I mean this in a nice way) the response comes through no issue at all - the issue is that the createSocket's methods of ObservableSSLSocketFactory are never called. Thanks for replying again! – brcolow Jul 01 '15 at 04:05
  • 1
    Hmm. There's some redundant code here. You don't need to call `setDefaultSSLSocketFactory()` or `SSLContext.setDefault().` You can set the protocols and cipher suites and truststore via system properties. – user207421 Jul 01 '15 at 04:31
  • Aye, thank you for pointing that out. I should have mentioned that I tried it with and without (both, neither, just one) of those two calls - this was a result of me scrambling to see if I could get the callSocket methods called. I have added the response to the log output for clarity. – brcolow Jul 01 '15 at 07:03
  • Just a thought : have you considered only decorating the Trust/KeyManagers interfaces, instead of overriding a whole SSLSocketFactory implementation ? Seems to me decorating an interface is far less intrusive than overriding an SSL implementation... – GPI Jul 06 '15 at 08:59

1 Answers1

-1

Before making the SSL handshake make sure the client and service have the correct certificates are installed on both(client and service) the server. Refer to url:http://www.programcreek.com/java-api-examples/index.php?api=javax.net.ssl.SSLSocketFactory for SSLFactory Implementation