Fixes #4366 - HTTP client uses SOCKS4 proxy hostname for SSL hostname verification.

Now setting correctly the host and port to the server destination
_after_ the SOCKS tunnel is established, similarly to what is done
for the HTTP CONNECT tunnel.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2019-12-03 21:40:55 +01:00
parent db9ad2fcec
commit 2ef02da1bd
2 changed files with 103 additions and 5 deletions

View File

@ -30,6 +30,7 @@ import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
@ -195,6 +196,8 @@ public class Socks4Proxy extends ProxyConfiguration.Proxy
try
{
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, destination.getHost());
context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, destination.getPort());
ClientConnectionFactory connectionFactory = this.connectionFactory;
if (destination.isSecure())
connectionFactory = destination.newSslClientConnectionFactory(null, connectionFactory);

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.client;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
@ -25,7 +27,12 @@ import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -44,7 +51,10 @@ public class Socks4ProxyTest
server = ServerSocketChannel.open();
server.bind(new InetSocketAddress("localhost", 0));
client = new HttpClient();
QueuedThreadPool clientThreads = new QueuedThreadPool();
clientThreads.setName("client");
client = new HttpClient(new SslContextFactory.Client());
client.setExecutor(clientThreads);
client.start();
}
@ -61,7 +71,7 @@ public class Socks4ProxyTest
int proxyPort = server.socket().getLocalPort();
client.getProxyConfiguration().getProxies().add(new Socks4Proxy("localhost", proxyPort));
final CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
byte ip1 = 127;
byte ip2 = 0;
@ -111,7 +121,7 @@ public class Socks4ProxyTest
"Content-Length: 0\r\n" +
"Connection: close\r\n" +
"\r\n";
channel.write(ByteBuffer.wrap(response.getBytes("UTF-8")));
channel.write(ByteBuffer.wrap(response.getBytes(StandardCharsets.UTF_8)));
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@ -123,7 +133,7 @@ public class Socks4ProxyTest
int proxyPort = server.socket().getLocalPort();
client.getProxyConfiguration().getProxies().add(new Socks4Proxy("localhost", proxyPort));
final CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
String serverHost = "127.0.0.13"; // Test expects an IP address.
int serverPort = proxyPort + 1; // Any port will do
@ -169,7 +179,92 @@ public class Socks4ProxyTest
"Content-Length: 0\r\n" +
"Connection: close\r\n" +
"\r\n";
channel.write(ByteBuffer.wrap(response.getBytes("UTF-8")));
channel.write(ByteBuffer.wrap(response.getBytes(StandardCharsets.UTF_8)));
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}
@Test
public void testSocks4ProxyWithTLSServer() throws Exception
{
String proxyHost = "localhost";
int proxyPort = server.socket().getLocalPort();
String serverHost = "127.0.0.13"; // Server host different from proxy host.
int serverPort = proxyPort + 1; // Any port will do.
SslContextFactory clientTLS = client.getSslContextFactory();
clientTLS.reload(ssl ->
{
// The client keystore contains the trustedCertEntry for the
// self-signed server certificate, so it acts as a truststore.
ssl.setTrustStorePath("src/test/resources/client_keystore.jks");
ssl.setTrustStorePassword("storepwd");
// Disable TLS hostname verification, but
// enable application hostname verification.
ssl.setEndpointIdentificationAlgorithm(null);
// The hostname must be that of the server, not of the proxy.
ssl.setHostnameVerifier((hostname, session) -> serverHost.equals(hostname));
});
client.getProxyConfiguration().getProxies().add(new Socks4Proxy(proxyHost, proxyPort));
CountDownLatch latch = new CountDownLatch(1);
client.newRequest(serverHost, serverPort)
.scheme(HttpScheme.HTTPS.asString())
.path("/path")
.send(result ->
{
if (result.isSucceeded())
latch.countDown();
else
result.getFailure().printStackTrace();
});
try (SocketChannel channel = server.accept())
{
int socks4MessageLength = 9;
ByteBuffer buffer = ByteBuffer.allocate(socks4MessageLength);
int read = channel.read(buffer);
assertEquals(socks4MessageLength, read);
// Socks4 response.
channel.write(ByteBuffer.wrap(new byte[]{0, 0x5A, 0, 0, 0, 0, 0, 0}));
// Wrap the socket with TLS.
SslContextFactory.Server serverTLS = new SslContextFactory.Server();
serverTLS.setKeyStorePath("src/test/resources/keystore.jks");
serverTLS.setKeyStorePassword("storepwd");
serverTLS.start();
SSLContext sslContext = serverTLS.getSslContext();
SSLSocket sslSocket = (SSLSocket)sslContext.getSocketFactory().createSocket(channel.socket(), serverHost, serverPort, false);
sslSocket.setUseClientMode(false);
// Read the request.
int crlfs = 0;
InputStream input = sslSocket.getInputStream();
while (true)
{
read = input.read();
if (read < 0)
break;
if (read == '\r' || read == '\n')
++crlfs;
else
crlfs = 0;
if (crlfs == 4)
break;
}
// Send the response.
String response =
"HTTP/1.1 200 OK\r\n" +
"Content-Length: 0\r\n" +
"Connection: close\r\n" +
"\r\n";
OutputStream output = sslSocket.getOutputStream();
output.write(response.getBytes(StandardCharsets.UTF_8));
output.flush();
assertTrue(latch.await(5, TimeUnit.SECONDS));
}