diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index fddd0af85bf..a295fe8735b 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -1160,11 +1160,26 @@ public class HttpClient extends ContainerLifeCycle return HttpScheme.HTTPS.is(scheme) || HttpScheme.WSS.is(scheme); } + /** + * Creates a new {@code SslClientConnectionFactory} wrapping the given connection factory. + * + * @param connectionFactory the connection factory to wrap + * @return a new SslClientConnectionFactory + * @deprecated use {@link #newSslClientConnectionFactory(SslContextFactory, ClientConnectionFactory)} instead + */ + @Deprecated protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory) { return new SslClientConnectionFactory(getSslContextFactory(), getByteBufferPool(), getExecutor(), connectionFactory); } + protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory) + { + if (sslContextFactory == null) + return newSslClientConnectionFactory(connectionFactory); + return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory); + } + private class ContentDecoderFactorySet implements Set { private final Set set = new HashSet<>(); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java index e1868cc54dc..fa0a3f50d1d 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java @@ -48,6 +48,7 @@ import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.DumpableCollection; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.util.thread.Sweeper; @@ -86,12 +87,12 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest { connectionFactory = proxy.newClientConnectionFactory(connectionFactory); if (proxy.isSecure()) - connectionFactory = newSslClientConnectionFactory(connectionFactory); + connectionFactory = newSslClientConnectionFactory(proxy.getSslContextFactory(), connectionFactory); } else { if (isSecure()) - connectionFactory = newSslClientConnectionFactory(connectionFactory); + connectionFactory = newSslClientConnectionFactory(null, connectionFactory); } this.connectionFactory = connectionFactory; @@ -132,9 +133,9 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest return new BlockingArrayQueue<>(client.getMaxRequestsQueuedPerDestination()); } - protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory) + protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory) { - return client.newSslClientConnectionFactory(connectionFactory); + return client.newSslClientConnectionFactory(sslContextFactory, connectionFactory); } public boolean isSecure() diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java index 057918a9c04..63d3946f1f0 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java @@ -54,6 +54,11 @@ public class HttpProxy extends ProxyConfiguration.Proxy super(address, secure); } + public HttpProxy(Origin.Address address, SslContextFactory.Client sslContextFactory) + { + super(address, sslContextFactory); + } + @Override public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory) { @@ -204,7 +209,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise); HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY); HttpClient client = destination.getHttpClient(); - ClientConnectionFactory sslConnectionFactory = client.newSslClientConnectionFactory(connectionFactory); + ClientConnectionFactory sslConnectionFactory = client.newSslClientConnectionFactory(null, connectionFactory); HttpConnectionOverHTTP oldConnection = (HttpConnectionOverHTTP)endPoint.getConnection(); context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, destination.getHost()); context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, destination.getPort()); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyConfiguration.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyConfiguration.java index d8e8acbbc05..972dd0f29fc 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyConfiguration.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyConfiguration.java @@ -22,10 +22,12 @@ import java.net.URI; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.util.HostPort; +import org.eclipse.jetty.util.ssl.SslContextFactory; /** * The configuration of the forward proxy to use with {@link org.eclipse.jetty.client.HttpClient}. @@ -64,11 +66,23 @@ public class ProxyConfiguration private final Set excluded = new HashSet<>(); private final Origin.Address address; private final boolean secure; + private final SslContextFactory.Client sslContextFactory; protected Proxy(Origin.Address address, boolean secure) + { + this(address, secure, null); + } + + public Proxy(Origin.Address address, SslContextFactory.Client sslContextFactory) + { + this(address, true, Objects.requireNonNull(sslContextFactory)); + } + + private Proxy(Origin.Address address, boolean secure, SslContextFactory.Client sslContextFactory) { this.address = address; this.secure = secure; + this.sslContextFactory = sslContextFactory; } /** @@ -87,6 +101,14 @@ public class ProxyConfiguration return secure; } + /** + * @return the optional SslContextFactory to use when connecting to proxies + */ + public SslContextFactory.Client getSslContextFactory() + { + return sslContextFactory; + } + /** * @return the list of origins that must be proxied * @see #matches(Origin) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java b/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java index 677867f1f6a..37b5f0b6769 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java @@ -64,7 +64,7 @@ public class Socks4Proxy extends ProxyConfiguration.Proxy } @Override - public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException + public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) { HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY); Executor executor = destination.getHttpClient().getExecutor(); @@ -198,7 +198,7 @@ public class Socks4Proxy extends ProxyConfiguration.Proxy HttpClient client = destination.getHttpClient(); ClientConnectionFactory connectionFactory = this.connectionFactory; if (destination.isSecure()) - connectionFactory = client.newSslClientConnectionFactory(connectionFactory); + connectionFactory = client.newSslClientConnectionFactory(null, connectionFactory); org.eclipse.jetty.io.Connection newConnection = connectionFactory.newConnection(getEndPoint(), context); getEndPoint().upgrade(newConnection); if (LOG.isDebugEnabled()) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java index b256901e143..d5c17170c12 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java @@ -474,9 +474,9 @@ public class HttpClientTLSTest client = new HttpClient(createClientSslContextFactory()) { @Override - protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory) + protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory) { - SslClientConnectionFactory ssl = (SslClientConnectionFactory)super.newSslClientConnectionFactory(connectionFactory); + SslClientConnectionFactory ssl = (SslClientConnectionFactory)super.newSslClientConnectionFactory(sslContextFactory, connectionFactory); ssl.setAllowMissingCloseMessage(false); return ssl; } diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ForwardProxyTLSServerTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ForwardProxyTLSServerTest.java index fb99d2a6571..a0c1cea1ae5 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ForwardProxyTLSServerTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ForwardProxyTLSServerTest.java @@ -689,6 +689,74 @@ public class ForwardProxyTLSServerTest } } + @Test + public void testBothProxyAndServerNeedClientAuthWithDifferentKeyStores() throws Exception + { + SslContextFactory.Server serverTLS = newServerSslContextFactory(); + serverTLS.setEndpointIdentificationAlgorithm(null); + serverTLS.setNeedClientAuth(true); + startTLSServer(serverTLS, new ServerHandler()); + int serverPort = serverConnector.getLocalPort(); + + SslContextFactory.Server proxyServerTLS = newProxySslContextFactory(); + proxyServerTLS.setEndpointIdentificationAlgorithm(null); + proxyServerTLS.setNeedClientAuth(true); + startProxy(proxyServerTLS); + int proxyPort = proxyConnector.getLocalPort(); + + SslContextFactory.Client clientTLS = new SslContextFactory.Client() + { + @Override + public SSLEngine newSSLEngine(String host, int port) + { + if (port != serverPort) + throw new IllegalStateException(); + return super.newSSLEngine(host, port); + } + }; + clientTLS.setKeyStorePath(MavenTestingUtils.getTestResourceFile("client_server_keystore.p12").getAbsolutePath()); + clientTLS.setKeyStorePassword("storepwd"); + clientTLS.setEndpointIdentificationAlgorithm(null); + HttpClient httpClient = new HttpClient(clientTLS); + + SslContextFactory.Client proxyClientTLS = new SslContextFactory.Client() + { + @Override + public SSLEngine newSSLEngine(String host, int port) + { + if (port != proxyPort) + throw new IllegalStateException(); + return super.newSSLEngine(host, port); + } + }; + proxyClientTLS.setKeyStorePath(MavenTestingUtils.getTestResourceFile("client_proxy_keystore.p12").getAbsolutePath()); + proxyClientTLS.setKeyStorePassword("storepwd"); + proxyClientTLS.setEndpointIdentificationAlgorithm(null); + proxyClientTLS.start(); + HttpProxy httpProxy = new HttpProxy(new Origin.Address("localhost", proxyConnector.getLocalPort()), proxyClientTLS); + httpClient.getProxyConfiguration().getProxies().add(httpProxy); + httpClient.start(); + + try + { + String body = "BODY"; + ContentResponse response = httpClient.newRequest("localhost", serverConnector.getLocalPort()) + .scheme(HttpScheme.HTTPS.asString()) + .method(HttpMethod.GET) + .path("/echo?body=" + URLEncoder.encode(body, "UTF-8")) + .timeout(5, TimeUnit.SECONDS) + .send(); + + assertEquals(HttpStatus.OK_200, response.getStatus()); + String content = response.getContentAsString(); + assertEquals(body, content); + } + finally + { + httpClient.stop(); + } + } + @Test @Tag("external") @Disabled diff --git a/jetty-proxy/src/test/resources/client_proxy_keystore.p12 b/jetty-proxy/src/test/resources/client_proxy_keystore.p12 new file mode 100644 index 00000000000..0b55322162e Binary files /dev/null and b/jetty-proxy/src/test/resources/client_proxy_keystore.p12 differ diff --git a/jetty-proxy/src/test/resources/client_server_keystore.p12 b/jetty-proxy/src/test/resources/client_server_keystore.p12 new file mode 100644 index 00000000000..f7b6729309f Binary files /dev/null and b/jetty-proxy/src/test/resources/client_server_keystore.p12 differ diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTimeoutTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTimeoutTest.java index 4b8aece4408..b8c60c07149 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTimeoutTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTimeoutTest.java @@ -254,9 +254,11 @@ public class HttpClientTimeoutTest extends AbstractTest scenario.client = new HttpClient(scenario.provideClientTransport(), sslContextFactory) { @Override - public ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory) + public ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory) { - return new SslClientConnectionFactory(getSslContextFactory(), getByteBufferPool(), getExecutor(), connectionFactory) + if (sslContextFactory == null) + sslContextFactory = getSslContextFactory(); + return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory) { @Override protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)