22

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.

Community
  • 1
  • 1
FD_
  • 12,947
  • 4
  • 35
  • 62
  • You have misunderstood. You don't need the outer socket. The proxy will speak SSL to the target. – user207421 Feb 06 '17 at 23:06
  • 1
    What proxy are you using that requires communications between client and proxy to be secure with SSL? This is very unusal. Typically a client would communicate with the proxy using an unsecured connection, and only after the proxy has connected to the target server would the client then initiate an SSL handshake with the target. The proxy is just a silent pass-through, the only encryption is between the client and target, not between the client and proxy. – Remy Lebeau Feb 07 '17 at 05:05
  • @RemyLebeau This might be unusual, but my setup requires to talk to the proxy via an SSL connection. – FD_ Feb 07 '17 at 09:56
  • Do you probably know how I can use a different SSL Provider as suggested in the solution to the linked question? – FD_ Feb 07 '17 at 09:59
  • I think I figured out how to use a different provider, but it did not help unfortunately. – FD_ Feb 07 '17 at 17:41
  • So your setup is wrong. The proxy should get a CONNECT request from the client and then conduct everything else in SSL, or rather let the peers conduct SSL and do nothing itself except copy bytes. – user207421 Feb 09 '17 at 10:18
  • @EJP The proxy does get a CONNECT from the client (via the previously established SSL connection) and then does nothing except copying bytes (via the SSL). However, once the proxy is connected to the destination server, I have to run another SSL handshake (this time with the destination server) for HTTPS. – FD_ Feb 09 '17 at 10:44
  • @EJP I've edited the question to include the steps the connection takes. As mentioned, the exact same code works on JRE 1.8 on a mac, so I'm hoping someone could hint me to what to change for it to work on Android, any difference in default parameters I have to change, etc. – FD_ Feb 09 '17 at 10:58
  • No you don't. The CONNECT happens *in plaintext,* then if it succeeds the client starts an SSL handshake *with the upstream server.* From that point on, al the proxy has to do is copy bytes in both directions. Anything else and your system is incorrectly written or incorrectly configured. HTTP proxy actions are already well-defined. – user207421 Feb 09 '17 at 11:12
  • @EJP I am referring to an HTTP proxy via SSL (sometimes referred to as HTTPS proxy, aka encrypted browser-proxy-connection or Secure Web Proxy). For reference, have a look at this squid feature http://wiki.squid-cache.org/Features/HTTPS#Encrypted_browser-Squid_connection or this Chromium page: http://dev.chromium.org/developers/design-documents/secure-web-proxy – FD_ Feb 09 '17 at 11:17
  • @EJP I'm not looking for comments on my system configuration (I have no control over the proxy and upstream servers). I am looking for advice why the above code (the SSLSocket via an SSLSocket) does not work on Android, when it does work in a desktop JRE. – FD_ Feb 09 '17 at 11:19
  • It doesn't work because your system doesn't work the way you think it does. You have misunderstood your system configuration. You don't need this. If you did, several network admins would need to be fired, and their managers. You're barking up the wrong tree. – user207421 Feb 09 '17 at 11:21
  • 1
    @EJP Trust me when I say I do need this. Abstract the exact use case: Why would SSL over SSL not work? – FD_ Feb 09 '17 at 11:23
  • @EJP This is actually a totally common scenario: Even Chromium supports Secure Web Proxies: http://dev.chromium.org/developers/design-documents/secure-web-proxy – FD_ Feb 09 '17 at 11:24
  • If your system worked the way you think it does, your code would work the way you think it should. – user207421 Feb 09 '17 at 11:44
  • @EJP Well, the exact same code does work on the JRE on a mac, so it must be different default parameters or an Android issue. And that's why I've posted the question. I know the system works the way I describe it. – FD_ Feb 09 '17 at 11:45
  • Have you read http://stackoverflow.com/questions/29916962/javax-net-ssl-sslhandshakeexception-javax-net-ssl-sslprotocolexception-ssl-han – Jon Goodwin Feb 11 '17 at 13:17
  • @JonGoodwin Thanks for your comment! I just tried all suggestions from the linked question. Unfortunately, none worked. The Play Services Security Provider did not make any difference, NoSSLv3SocketFactory lead to an Exception at second handshake, reporting the Socket to be closed (which my code certainly doesn't do). – FD_ Feb 11 '17 at 15:39
  • is there a way you can provide a tcpdump? – manishg Feb 12 '17 at 03:44
  • @manishg I'll try to capture a tcpdump. – FD_ Feb 12 '17 at 10:14
  • @manishg I have now created a GitHub repo with a sample project and tcpdumps. Have a look at the tcpdump directory in the repo. Dump setup description is in the readme file. – FD_ Feb 12 '17 at 19:13
  • @FD_ Were you ever able to solve this issue? – hopia Jun 24 '18 at 03:10
  • It seems like recent versions of Conscrypt (the standalone version) fixed the issue. – FD_ Jun 24 '18 at 08:29

5 Answers5

7

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.

Hod
  • 2,236
  • 1
  • 14
  • 22
  • Thanks for your answer! I just tried with every protocol of [SSLv3, TLSv1, TLSv1.1, TLSv1.2] (the supported protocols), but unfortunately, all of the tls versions produced the error (and my server does not seem to support SSL). – FD_ Feb 10 '17 at 19:26
  • Do you have some background knowledge on SSL/TLS? Would you mind having a look at a minimal complete verifiable example project? – FD_ Feb 10 '17 at 19:27
  • I might have a chance to look. Can you set up a SO chat to continue? – Hod Feb 11 '17 at 23:47
  • Thanks! I created a GitHub repo with an example project here: https://github.com/FD-/SSLviaSSL. Chat is here: http://chat.stackoverflow.com/rooms/info/135519/discussion-on-sslsocket-via-another-sslsocket?tab=general – FD_ Feb 12 '17 at 19:11
  • Try javax.net.ssl.HttpsURLConnection in place of SSLSocket. Haven't had a chance to test it, but Google info suggests it may be getting updated more. – Hod Feb 16 '17 at 09:18
  • Can I use HttpsURLConnection via a Secure Web Proxy? I think that's not possible. – FD_ Feb 16 '17 at 09:20
  • Haven't tried it yet, but check out this post: http://stackoverflow.com/questions/15927079/how-to-use-httpsurlconnection-through-proxy-by-setproperty You want to set the https.proxyHost and https.proxyPort (instead of http). This info may help too: https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html Also, turn on full debugging with System.setProperty("javax.net.debug", "all"); – Hod Feb 16 '17 at 23:22
  • Added some notes in the chat, too. – Hod Feb 16 '17 at 23:23
  • https.proxy* is for telling the JRE to use the CONNECT message for HTTPS connections (insecure client-proxy connection still), it doesn't work for Secure Web Proxies AFAICT. Beside that, I'm afraid the System Properties (javax.net.debug) don't work with Android (it seems to be implementation-specific to the JRE SSL Provider). – FD_ Feb 17 '17 at 09:02
7

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.
manishg
  • 9,520
  • 1
  • 16
  • 19
  • Thanks for your detailed answer! Did you possibly find out what specific code in the SSLFactory or SSLSocketImpl is causing the issue? The code of the conscrypt provider used by Android seems to be here: https://android.googlesource.com/platform/external/conscrypt/ – FD_ Feb 14 '17 at 16:40
  • I don't know which particular code would be causing but I suspect some commits like this (https://github.com/android/platform_frameworks_base/commit/725a4a71b8f2a5493628d87556c78860f66d2308) would be causing it. – manishg Feb 14 '17 at 20:20
  • @manishg those are your own captures, right? Looking at the captures supplied by FD_, I see something a little different. The setup described has the packets from stunnel to squid in the clear on localhost, so you can see what's happening. Things seem to fall apart starting at frame 24, when the client sends a second "client hello", as you describe, except I see no server extension. It's immediately followed by an encrypted alert, indicating a failure. So similar issue that Android tries a second connection unencrypted, but to the proxy. – Hod Feb 15 '17 at 17:57
  • 1
    Yeah those are my captures from the device/JRE. Even from FD_ captures, we see the same thing. In android case, we can see 2 "client hello" messages in the wireshark capture coming from android client. while in JRE case there is only one client hello coming from client. The other "client hello" is something stunnel sending to proxy. This proves the fact that stunnel received a encrypted client hello and after decrypting it, it forwards client hello to proxy. I will add an edit to my answer to add these details as well. – manishg Feb 15 '17 at 21:18
0

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

Community
  • 1
  • 1
Abdullah Raza
  • 600
  • 3
  • 9
  • It seems like you did not understand what my code does. I am using the http CONNECT message, which simply establishes a connection and then copies between client and destination. – FD_ Feb 16 '17 at 09:01
0

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);
    }
}
Jon Goodwin
  • 9,053
  • 5
  • 35
  • 54
  • I think I got this exception when running the app on Android 4.1. I guess the difference in the error message stems from the different SSL providers that changed between the Android versions. – FD_ Feb 16 '17 at 21:13
0

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

FD_
  • 12,947
  • 4
  • 35
  • 62