Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enabling specific SSL protocols with Android WebViewClient

My application uses WebViewClient to make SSL connections to the server. The server is configured to only accept TLSv1.1 and above protocols.

  1. How do I check which SSL protocols are a) Supported and b) Enabled by default when using Android WebViewClient on a device.
  2. How do I enable specific SSL protocols for Android WebViewClient instance used in my application.

On one of the test devices running Android 4.3, WebViewClient throws onReceivedError callback with the following description:

"Failed to perform SSL handshake"

Chrome logs are as follows:

01-29 15:58:00.073 5486 5525 W chromium_net: external/chromium/net/http/http_stream_factory_impl_job.cc:865: [0129/155800:WARNING:http_stream_factory_impl_job.cc(865)] Falling back to SSLv3 because host is TLS intolerant: 10.209.126.125:443 01-29 15:58:00.083 5486 5525 E chromium_net: external/chromium/net/socket/ssl_client_socket_openssl.cc:792: [0129/155800:ERROR:ssl_client_socket_openssl.cc(792)] handshake failed; returned 0, SSL error code 5, net_error -107 

My application also uses HttpClient and HttpsUrlConnection classes to setup SSL Connections. I was able to use SSLSocket API to enable specific protocols when using these classes. http://developer.android.com/reference/javax/net/ssl/SSLSocket.html#setEnabledProtocols(java.lang.String[])

I need to do the same with WebViewClient.

like image 802
user802467 Avatar asked Feb 04 '15 19:02

user802467


3 Answers

As per documenation it is NOT possible to support TLS 1.0 in WebView in Android < 4.3. For Android 4.4 it is disabled by default.

Check this chart for support of TLS 1.0 in different browsers: https://en.wikipedia.org/wiki/Transport_Layer_Security#Web_browsers

like image 161
M. C. Avatar answered Oct 16 '22 17:10

M. C.


If your app is using, or you are willing to use, Google Play services, you can use newer security features on older phones by installing their Provider. It is easy to install, only one line (plus exception handling, etc). You will also need to add google play services to your gradle file if you do not already have it. ProviderInstaller is included in the -base package.

try {
    ProviderInstaller.installIfNeeded(this);
} catch (GooglePlayServicesRepairableException e) {
     // Fix it
} catch (GooglePlayServicesNotAvailableException e) {
     // Skip it
}

For a full example, see "Updating Your Security Provider to Protect Against SSL Exploits" from Google.

like image 37
iagreen Avatar answered Oct 16 '22 16:10

iagreen


Actually, I managed to make it work, but you need okHttp library for that. Try this when you're setting up browser activity:

    WebViewClient client = new WebViewClient() {
        private OkHttpClient okHttp = new OkHttpClient.Builder().build();

        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
            Request okHttpRequest = new Request.Builder().url(url).build();
            try {
                Response response = okHttp.newCall(okHttpRequest).execute();
                return new WebResourceResponse(response.header("Content-Type", "plain/text"), response.header("Content-Encoding", "deflate"), response.body().byteStream());
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    };
    webView.setWebViewClient(client);

Also, you'll need classic Trust Manager Manipulator, SSL socket factory and its implementation in your Application class:

public class TrustManagerManipulator implements X509TrustManager {


    private static TrustManager[] trustManagers;
    private static final X509Certificate[] acceptedIssuers = new X509Certificate[] {};

    public boolean isClientTrusted(X509Certificate[] chain) {
        return true;
    }

    public boolean isServerTrusted(X509Certificate[] chain) {
        return true;
    }

    public static void allowAllSSL()
    {

        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });
        SSLContext context = null;
        if (trustManagers == null) {
            trustManagers = new TrustManager[] { new TrustManagerManipulator() };
        }
        try {
            context = SSLContext.getInstance("TLS");
            context.init(null, trustManagers, new SecureRandom());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        HttpsURLConnection.setDefaultSSLSocketFactory(context
                .getSocketFactory());
    }

    public void checkClientTrusted(X509Certificate[] chain, String authType)
            throws CertificateException {
    }

    public void checkServerTrusted(X509Certificate[] chain, String authType)
            throws CertificateException {
    }

    public X509Certificate[] getAcceptedIssuers() {
        return acceptedIssuers;
    }
}

SSl Socket Factory:

public class TLSSocketFactory extends SSLSocketFactory {

    private SSLSocketFactory internalSSLSocketFactory;

    public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException {
        SSLContext context = SSLContext.getInstance("TLS");
        TrustManager[] managers = new TrustManager[] { new TrustManagerManipulator() };
        context.init(null, managers, new SecureRandom());
        internalSSLSocketFactory = context.getSocketFactory();
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return internalSSLSocketFactory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return internalSSLSocketFactory.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
    }

    private Socket enableTLSOnSocket(Socket socket) {
        if(socket != null && (socket instanceof SSLSocket)) {
            ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"});
        }
        return socket;
    }
}

App class:

public class App extends Application {
    private static App appInstance;

    @Override
    public void onCreate() {
        super.onCreate();

        setupSSLconnections();
    }

    private void setupSSLconnections() {
        try {
            HttpsURLConnection.setDefaultSSLSocketFactory(new TLSSocketFactory());
        } catch (KeyManagementException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
}
like image 3
Alex Avatar answered Oct 16 '22 17:10

Alex