Skip to content

Commit b6bb453

Browse files
committed
Working double handshake
1 parent 09906fd commit b6bb453

5 files changed

Lines changed: 286 additions & 32 deletions

File tree

src/main/java/io/split/android/client/network/HttpOverTunnelExecutor.java

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.Map;
1010

1111
import javax.net.ssl.SSLSocket;
12+
import javax.net.ssl.SSLSocketFactory;
1213

1314
import io.split.android.client.utils.logger.Logger;
1415

@@ -42,6 +43,7 @@ public HttpOverTunnelExecutor() {
4243
* @param method HTTP method for the request
4344
* @param headers Headers to include in the request
4445
* @param body Request body (null for GET requests)
46+
* @param combinedSslSocketFactory The combined SSL socket factory to use for HTTPS origins
4547
* @return HttpResponse containing the server's response
4648
* @throws IOException if the request execution fails
4749
*/
@@ -51,21 +53,22 @@ public HttpResponse executeRequest(
5153
@NonNull URL targetUrl,
5254
@NonNull HttpMethod method,
5355
@NonNull Map<String, String> headers,
54-
@Nullable String body) throws IOException {
56+
@Nullable String body,
57+
@NonNull SSLSocketFactory combinedSslSocketFactory) throws IOException {
5558

56-
Logger.v("Executing " + method + " request to " + targetUrl + " through SSL tunnel");
59+
Logger.v("Executing request through SSL tunnel to: " + targetUrl);
5760

5861
try {
5962
if ("https".equalsIgnoreCase(targetUrl.getProtocol())) {
6063
// For HTTPS origins, we still need to establish SSL connection to origin
61-
// through the tunnel
62-
return executeHttpsRequest(tunnelSocket, targetUrl, method, headers, body);
64+
// through the tunnel using the combined SSL socket factory
65+
return executeHttpsRequest(tunnelSocket, targetUrl, method, headers, body, combinedSslSocketFactory);
6366
} else {
6467
// For HTTP origins, send request directly through tunnel
6568
return executeHttpRequest(tunnelSocket, targetUrl, method, headers, body);
6669
}
67-
68-
} catch (IOException e) {
70+
} catch (Exception e) {
71+
Logger.e("Failed to execute request through tunnel: " + e.getMessage());
6972
e.printStackTrace();
7073
throw new IOException("Failed to execute HTTP request through tunnel to " + targetUrl, e);
7174
}
@@ -80,39 +83,45 @@ private HttpResponse executeHttpsRequest(
8083
@NonNull URL targetUrl,
8184
@NonNull HttpMethod method,
8285
@NonNull Map<String, String> headers,
83-
@Nullable String body) throws IOException {
86+
@Nullable String body,
87+
@NonNull SSLSocketFactory combinedSslSocketFactory) throws IOException {
8488

8589
Logger.v("Establishing SSL connection to HTTPS origin through tunnel");
8690

87-
// The tunnel is established and working (we're past the SSL protocol errors).
88-
// The issue now is that we're sending HTTP requests to an HTTPS origin.
89-
// We need to perform SSL handshake with the origin server through the tunnel.
91+
// After CONNECT, the tunnel is transparent and we need to establish
92+
// a new SSL connection to the origin server through the tunnel.
93+
// This implements the "onion layering" architecture:
94+
// - Outer layer: SSL connection to proxy (already established)
95+
// - Inner layer: SSL connection to origin (through tunnel)
9096

9197
try {
92-
// Create SSL context and engine for manual SSL handling
93-
javax.net.ssl.SSLContext sslContext = javax.net.ssl.SSLContext.getInstance("TLS");
94-
sslContext.init(null, null, null);
95-
96-
javax.net.ssl.SSLEngine sslEngine = sslContext.createSSLEngine(targetUrl.getHost(), getTargetPort(targetUrl));
97-
sslEngine.setUseClientMode(true);
98-
99-
// Begin SSL handshake
100-
sslEngine.beginHandshake();
98+
// Use the same combined SSL socket factory that was used for the tunnel
99+
// This factory contains both proxy CA and origin CA certificates
100+
// so it can validate both proxy and origin server certificates
101101

102-
// Get the tunnel socket's streams
103-
java.io.InputStream tunnelInput = tunnelSocket.getInputStream();
104-
java.io.OutputStream tunnelOutput = tunnelSocket.getOutputStream();
102+
// Create SSL socket to origin server using tunnel socket as the underlying transport
103+
// This uses the correct SSLSocketFactory.createSocket(Socket, String, int, boolean) method
104+
javax.net.ssl.SSLSocket originSslSocket = (javax.net.ssl.SSLSocket)
105+
combinedSslSocketFactory.createSocket(
106+
tunnelSocket, // Use tunnel socket as underlying transport
107+
targetUrl.getHost(), // Origin server hostname
108+
getTargetPort(targetUrl), // Origin server port
109+
false // Don't auto-close underlying socket
110+
);
105111

106-
// Perform SSL handshake using the tunnel streams
107-
performSslHandshake(sslEngine, tunnelInput, tunnelOutput);
112+
// Configure SSL socket for client mode
113+
originSslSocket.setUseClientMode(true);
108114

109-
Logger.v("SSL handshake with origin server completed through tunnel");
115+
// Perform SSL handshake with origin server through tunnel
116+
Logger.v("Performing SSL handshake with origin server through tunnel");
117+
originSslSocket.startHandshake();
118+
Logger.v("SSL handshake with origin server completed successfully");
110119

111-
// Now we can send HTTP requests through the SSL engine
112-
sendHttpRequestThroughSsl(sslEngine, tunnelInput, tunnelOutput, targetUrl, method, headers, body);
120+
// Now send HTTP request through the SSL connection to origin
121+
sendHttpRequest(originSslSocket, targetUrl, method, headers, body);
113122

114-
// Read and parse HTTP response through SSL engine
115-
HttpResponse response = readHttpResponseThroughSsl(sslEngine, tunnelInput, tunnelOutput);
123+
// Read and parse HTTP response from origin
124+
HttpResponse response = mResponseParser.parseHttpResponse(originSslSocket.getInputStream());
116125

117126
Logger.v("HTTPS request completed successfully, status: " + response.getHttpStatus());
118127
return response;

src/main/java/io/split/android/client/network/ProxyCacertConnectionHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ public HttpResponse executeRequest(@NonNull HttpProxy httpProxy,
7777
targetUrl,
7878
method,
7979
headers,
80-
body
80+
body,
81+
sslSocketFactory // Pass the combined SSL socket factory for HTTPS origins
8182
);
8283

8384
Logger.v("PROXY_CACERT request completed successfully, status: " + response.getHttpStatus());

src/main/java/io/split/android/client/network/SslProxyTunnelEstablisher.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,22 @@ public SSLSocket establishTunnel(@NonNull String proxyHost,
5454
// Step 1: Create raw socket connection to proxy
5555
System.out.println("Step 1: Creating raw socket connection to proxy...");
5656
rawSocket = new Socket(proxyHost, proxyPort);
57+
rawSocket.setSoTimeout(10000); // 10 second timeout
5758
System.out.println("Raw socket connected to proxy: " + proxyHost + ":" + proxyPort);
5859

5960
// Step 2: Create SSL engine for proxy authentication using custom CA certificates
60-
System.out.println("Step 2: Creating SSL engine for proxy authentication...");
61+
System.out.println("Step 2: Creating SSL socket for proxy authentication...");
6162

6263
// We need to use the provided SSLSocketFactory which contains the custom CA certificates
6364
// Create a temporary SSL socket to establish the SSL session with proper trust validation
6465
sslSocket = (SSLSocket) sslSocketFactory.createSocket(rawSocket, proxyHost, proxyPort, false);
6566
sslSocket.setUseClientMode(true);
67+
sslSocket.setSoTimeout(10000); // 10 second timeout
68+
System.out.println("SSL socket created successfully");
6669

6770
// Perform SSL handshake using the SSL socket with custom CA certificates
6871
System.out.println("Performing SSL handshake with custom CA certificates...");
72+
System.out.println("About to call sslSocket.startHandshake()...");
6973
sslSocket.startHandshake();
7074
System.out.println("SSL handshake completed successfully with custom CA validation");
7175

src/test/java/io/split/android/client/network/HttpClientTunnellingProxyTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,8 @@ public void run() {
370370

371371
javax.net.ssl.SSLServerSocketFactory factory = sslContext.getServerSocketFactory();
372372
mServerSocket = factory.createServerSocket(mPort);
373+
mPort = mServerSocket.getLocalPort(); // Update mPort with the actual assigned port
374+
373375
// Don't require client auth - this is server-only SSL
374376
((javax.net.ssl.SSLServerSocket) mServerSocket).setWantClientAuth(false);
375377
((javax.net.ssl.SSLServerSocket) mServerSocket).setNeedClientAuth(false);
@@ -444,7 +446,7 @@ private static class TunnelProxy extends Thread {
444446
// Latch to signal that a CONNECT tunnel was established
445447
private final CountDownLatch mTunnelLatch = new CountDownLatch(1);
446448
// Port to listen on (0 = auto-assign)
447-
protected final int mPort;
449+
protected int mPort;
448450
// The server socket for accepting connections
449451
public ServerSocket mServerSocket;
450452
// Flag to control proxy shutdown

0 commit comments

Comments
 (0)