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.
- How do I check which SSL protocols are a) Supported and b) Enabled by default when using Android
WebViewClient
on a device. - 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
.
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
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.
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();
}
}
}