java – SSLSocket via another SSLSocket-ThrowExceptions

Exception or error:

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.

  • What could I be doing wrong?
  • How could I further debug the issue?
  • Does anyone have a working example of an SSLSocket going over another
    SSLSocket on a recent Android version?

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:

  1. From the Android app, make a connection to the Secure Proxy server (Socket)
  2. Do an SSL/TLS handshake with the Proxy server (SSLSocket over Socket)
  3. Via the SSLSocket, send the CONNECT message to the Proxy server
  4. Proxy connects to destination (https) server and only copies bytes from now on
  5. Do an SSL/TLS handshake with the destination (https) server (SSLSocket over SSLSocket over Socket)
  6. 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.

How to solve:

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:

enter image description here

  • 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:

enter image description here

  • 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.
    enter image description here
  • 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:
enter image description here

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
enter image description here

Now let’s look at android client case.
enter image description here

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.

enter image description here

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:

  1. 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.

###

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.

HTTPS connections over proxy servers

###

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);
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *