I'm trying to create an SSLSocket
on top of another SSLSocket
in an Android app. The lower connection is an SSL-secured connection to a Secure Web Proxy (HTTP proxy over SSL), the upper connection is for HTTP over SSL (HTTPS).
For this, I'm using SSLSocketFactory's createSocket()
function that allows to pass an existing Socket over which to run the SSL connection like this:
private Socket doSSLHandshake(Socket socket, String host, int port) throws IOException {
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager(){
public X509Certificate[] getAcceptedIssuers(){ return null; }
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}
};
try {
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new SecureRandom());
SSLSocket sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(socket, host, port, true);
sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols());
sslSocket.setEnableSessionCreation(true);
sslSocket.startHandshake();
return sslSocket;
} catch (KeyManagementException | NoSuchAlgorithmException e) {
throw new IOException("Could not do handshake: " + e);
}
}
This code is working fine when the underlying socket is a normal tcp Socket, but when I use as underlying socket an SSLSocket that has been created using the above code before, the handshake fails with the following exception:
javax.net.ssl.SSLHandshakeException: Handshake failed
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:429)
at com.myapp.MyThreadClass.doSSLHandshake(MyThreadClass.java:148)
at com.myapp.MyThreadClass.run(MyThreadClass.java:254)
Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x7374d56e80: Failure in SSL library, usually a protocol error
error:100000e3:SSL routines:OPENSSL_internal:UNKNOWN_ALERT_TYPE (external/boringssl/src/ssl/s3_pkt.c:618 0x738418ce7e:0x00000000)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:357)
... 2 more
I'm testing on Android 7.1.1. The app is targeting SDK level 23.
Any help is greatly appreciated!
Update: The very same code works in JRE 1.8 on a Mac, but not on Android.
Update 2: Conceptually, these are the steps the connection goes through:
- From the Android app, make a connection to the Secure Proxy server (Socket)
- Do an SSL/TLS handshake with the Proxy server (SSLSocket over Socket)
- Via the SSLSocket, send the CONNECT message to the Proxy server
- Proxy connects to destination (https) server and only copies bytes from now on
- Do an SSL/TLS handshake with the destination (https) server (SSLSocket over SSLSocket over Socket)
- Send the GET message to the destination server and read response
The problem arises in step 5, when doing the handshake on an SSLSocket that goes over an SSLSocket (that goes over a Socket).
Update 3: I have now opened a GitHub repo with a sample project and tcpdumps: https://github.com/FD-/SSLviaSSL
Note: I have found and read a question with very similar title, but it did not contain much useful help unfortunately.
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.
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:
Now let's look at the android tcpdump:
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:
Since HTTPS make sure no middle man interrupt the communication between the two. thats why you are unable to do so. so tha second handshake fails.
Here is the link might help.
I don't know if this helps but:
I've built your repo test environment, and my error is slightly different:
I/SurfaceTextureClient(20733): [0x52851b98] frames:2, duration:1.005000, fps:1.989805
I/System.out(20733): [socket][2] connection /192.168.1.100:10443;LocalPort=35380(0)
I/System.out(20733): [CDS]connect[/192.168.1.100:10443] tm:90
I/System.out(20733): [socket][/192.168.1.123:35380] connected
I/System.out(20733): Doing SSL handshake with 192.168.1.100:10443
I/System.out(20733): Supported protocols are: [SSLv3, TLSv1, TLSv1.1, TLSv1.2]
E/NativeCrypto(20733): ssl=0x53c96268 cert_verify_callback x509_store_ctx=0x542e0a80 arg=0x0
E/NativeCrypto(20733): ssl=0x53c96268 cert_verify_callback calling verifyCertificateChain authMethod=RSA
I/System.out(20733): Doing SSL handshake with 192.168.1.100:443
I/System.out(20733): Supported protocols are: [SSLv3, TLSv1, TLSv1.1, TLSv1.2]
E/NativeCrypto(20733): Unknown error during handshake
I/System.out(20733): Shutdown rx/tx
I/System.out(20733): [CDS]close[35380]
I/System.out(20733): close [socket][/0.0.0.0:35380]
W/System.err(20733): javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException:
SSL handshake aborted: ssl=0x53c9c1d8:
Failure in SSL library, usually a protocol error
W/System.err(20733): error:140770FC:SSL routines:
SSL23_GET_SERVER_HELLO:
unknown protocol (external/openssl/ssl/s23_clnt.c:766 0x4e7cb3ad:0x00000000)
W/System.err(20733): at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:413)
W/System.err(20733): at com.bugreport.sslviassl.SecureWebProxyThread.doSSLHandshake(SecureWebProxyThread.java:147)
W/System.err(20733): at com.bugreport.sslviassl.SecureWebProxyThread.run(SecureWebProxyThread.java:216)
W/System.err(20733): Caused by: javax.net.ssl.SSLProtocolException:
SSL handshake aborted: ssl=0x53c9c1d8:
Failure in SSL library, usually a protocol error
W/System.err(20733): error:140770FC:SSL routines:
SSL23_GET_SERVER_HELLO:unknown protocol (external/openssl/ssl/s23_clnt.c:766 0x4e7cb3ad:0x00000000)
W/System.err(20733): at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method)
W/System.err(20733): at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:372)
W/System.err(20733): ... 2 more
I/SurfaceTextureClient(20733): [0x52851b98] frames:5, duration:1.010000, fps:4.946089
One thought on threads: what thread are you running your doSSLHandshake() method on ?
public void policy()
{
int SDK_INT = android.os.Build.VERSION.SDK_INT;
if (SDK_INT > 8)
{
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
}
This indeed turned out to be a bug in the default SSL providers on any Android device I tested (my newest device is a Nexus 9 running Android 7.1.1 though). Eventually, I found that the engine-based SSLSocket implementation (freshly released at the time) of the standalone version of the Conscrypt SSL provider worked on Android in exactly the way I expected it to work.
For further details, have a look at the discussion with Conscrypt maintainers on GitHub: https://github.com/google/conscrypt/issues/104
User contributions licensed under CC BY-SA 3.0