Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Jersey Doesn't Use HttpUrlConnectorProvider with custom SSLSocketFactory #3171

Closed
jerseyrobot opened this issue Jul 1, 2015 · 7 comments
Closed

Comments

@jerseyrobot
Copy link
Contributor

I am trying to use Jersey + JDK's Http(s)UrlConnection to be able to be notified when SSL handshakes occur via an HttpsUrlConnection through a Jersey client. The request goes through (using SSL), and the response comes back (using SSL) but Jersey is not using the SSLSocketFactory that I provide via a connectorProvider that opens an HttpsUrlConnection and calls setSSLSocketFactory() on that connection. The code should make this more 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()
.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.867CREATING OBSERVABLE SOCKET FACTORY!
00:01:38.072Observable socket factory created
00:01:38.073 preferredCipherSuites: [TLS_ECDHE_ECDSA_WITH256...(snip)]
00:01:38.073 preferredProcotols: [TLSv1, TLSv1.1, TLSv1.2]
00:01:39.435 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.

I would expect that the log would contain the entries:

creating ssl socket
Handshake successful!
using cipher suite: (blah)

But in fact they don't show up because the code is not executed.

Environment

Windows/Mac OS X, JDK 8u60

Affected Versions

[2.19]

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
Reported by brcolow

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
@AdamLindenthal said:
Hi, thanks for reporting the issue and for sharing the test case.
I will move the issue to backlog, so that we can plan the work on it.

Regards,
Adam

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
petrbouda said:
Hi,

I tried to reproduce your bug, but unsuccessfully. Could you provide an entire test using JerseyTest class, which should be easily runnable? I came across several problems when I was trying to compose some test from your provided examples.

Thank you,

Petr

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
petrbouda said:
HttpUrlConnection is able to register a custom socket factory

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
Marked as fixed on Wednesday, August 26th 2015, 5:06:02 am

@jerseyrobot
Copy link
Contributor Author

@glassfishrobot Commented
This issue was imported from java.net JIRA JERSEY-2899

@jerseyrobot
Copy link
Contributor Author

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

No branches or pull requests

1 participant