SSLSocket via another SSLSocket
So I tried to find out what goes wrong in case of android but so far I didn't find anything wrong with your code. Also since the code works for JRE, it asserts the hypothesis.
From the tcpdump you provided, there is substantial information to conclude how Android behaves with the same set of APIs as JRE.
Let's have a look at JRE tcpdump:
- See the initial handshake messages (Client Hello, Server Hello, change cipher spec). This shows the handshake between JRE client and proxy server. This is successful.
- Now we don't see the second handshake between JRE client and www.google.com (the end server) as that's encrypted as we are doing SSL over SSL. The proxy server is copying them bit by bit to the end server. So this is a correct behavior.
Now let's look at the android tcpdump:
- See the initial handshake messages (Client Hello, Server Hello, change cipher spec). This shows the handshake between android client and proxy server. This is successful.
- Now ideally we shouldn't see the second handshake as it should be encrypted. But here we can see that android client is sending a "client hello" and it is sending it to "www.google.com" even though the packet is sent to the proxy server.
- The above is bound to fail as the packet was supposed to be written over SSL socket and not over the initial plain socket. I reviewed your code and I see that you are doing the second handshake over SSLSocket and not over plain socket.
Analysis from proxy/stunnel wireshark:
JRE Case:
In case of JRE, the client does the initial SSL handshake with the stunnel/proxy server. The same can be seen below:
The handshake is successful and connection is done
Then the client tries to connect to remote server (www.google.com) and starts the handshake. So the client hello sent by client is seen as encrypted message in packet #34 and when stunnel decrypts the same, it is seen at "Client hello" which is forwarded to proxy server by stunnel
Now let's look at android client case.
Initial SSL handshake from client to stunnel/proxy is successful as seen above.
Then when android client starts the handshake with remote (www.google.com), ideally it should use SSL socket for the same. If this was the case, we should see encrypted traffic from android to stunnel (similar to packet #34 from JRE case), stunnel should decrypt and send "client hello" to proxy. however as you can see below, android client is sending a "client hello" over plain socket.
If you compare the packet #24 with packet #34 from JRE, we can spot this difference.
Conclusion:
This is a bug with android SSL (factory.createsocket()
with SSL socket) implementation and I feel there may not be a magical workaround for the same using the same set of APIs. In fact I found this issue in android bug list. See below link:
https://code.google.com/p/android/issues/detail?id=204159
This issue is still unresolved and you can probably follow-up with android dev team to fix the same.
Possible Solutions:
If we conclude that the same set of APIs cannot work then you are left with only one option:
- Write your own SSL wrapper over the SSL socket. You can manually do handshake or use a third party implementation. This could take a while but looks like the only way.
I don't think you're doing anything wrong. It looks like there's a bug in the protocol negotiation during your second handshake. A good candidate would be failing in an NPN TLS handshake extension.
Take a look at your protocols in this call: sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols());
You can walk through the listed protocols and try them individually. See if you can lock down what's failing and whether you need that specific protocol or extension supported.