javaandroidsslcryptographysslsocketfactory

SSLSocket via another SSLSocket


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:

  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.


Solution

  • 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