使用 Android WebViewClient 启用特定 SSL 协议

Posted

技术标签:

【中文标题】使用 Android WebViewClient 启用特定 SSL 协议【英文标题】:Enabling specific SSL protocols with Android WebViewClient 【发布时间】:2015-04-04 11:07:13 【问题描述】:

我的应用程序使用WebViewClient 与服务器建立 SSL 连接。 服务器配置为仅接受 TLSv1.1 及以上协议。

    在设备上使用 android WebViewClient 时,如何检查哪些 SSL 协议 a) 受支持和 b) 默认启用。 如何为我的应用程序中使用的 Android WebViewClient 实例启用特定 SSL 协议。

在其中一台运行 Android 4.3 的测试设备上, WebViewClient 抛出 onReceivedError 回调,描述如下:

“未能执行 SSL 握手”

Chrome日志如下:

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 

我的应用程序还使用 HttpClientHttpsUrlConnection 类来设置 SSL 连接。在使用这些类时,我能够使用SSLSocket API 来启用特定协议。 http://developer.android.com/reference/javax/net/ssl/SSLSocket.html#setEnabledProtocols(java.lang.String[])

我需要对 WebViewClient 做同样的事情。

【问题讨论】:

AFAIK 启用的协议 WebView 不能被应用程序更改。支持或不支持哪些协议取决于 Android 版本。对于 TLS 1.1 及更高版本,您需要 Android 4.4 或更高版本(请参阅here)。 有人知道这个吗?我目前正在开展一个项目,我们需要为三个非生产测试服务器从 TLS v1.2 回退到 1.1。我们正在尝试使用这样的东西: adb shell 'echo "browser --show-fps-counter --ssl-version-min=tls1.1 --ssl-version-max=tls1.1" > /data/ local/tmp/webview-command-line' FPS 计数器显示在 Android L 平板电脑上,但不在 Android TV 上(这很奇怪,因为它们应该是相同的 webview 代码)。 ssl-version 标志似乎不起作用;从设备捕获的 tcpdump 仍显示正在使用 TLS v1.2。任何帮助都会很棒。 @user802467 您找到问题的解决方案了吗?我也面临同样的问题 @KK_07k11A0585 不,我找不到在 WebViewClient 中配置它的方法。我认为罗伯特的上述评论是正确的。 blog.dev-area.net/2015/08/13/… 【参考方案1】:

这是因为 android 4.3 不支持 TSL 1.1 但只支持 TSL1.0 阅读这篇文章 https://www.ssllabs.com/ssltest/clients.html find android 4.3 会看到

协议 TLS 1.3 否 TLS 1.2 否 TLS 1.1 否 TLS 1.0 是 SSL 3 不安全 是 SSL 2 否

【讨论】:

欢迎来到 SO。您的回答似乎与对 OP 的第一条评论大致相同。请参阅***.com/help/how-to-answer 获取指导。【参考方案2】:

根据文档,在 Android

查看此图表以了解不同浏览器对 TLS 1.0 的支持:https://en.wikipedia.org/wiki/Transport_Layer_Security#Web_browsers

【讨论】:

您的意思是“...支持 TLS 1.1”(而不是 1.0)? 它说 Disabled by default​ 从 4.1 到 4.4。那么如何开启呢?【参考方案3】:

实际上,我设法让它工作,但你需要 okHttp 库。 在设置浏览器活动时试试这个:

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

此外,您还需要经典的 Trust Manager Manipulator、SSL 套接字工厂及其在 Application 类中的实现:

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 套接字工厂:

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;
    

应用类:

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

【讨论】:

不幸的是,我的 webView 中只看到纯文本;它可能取决于javascript的执行。还发现了这篇文章artemzin.com/blog/android-webview-io @ar-g 你的问题解决了吗?如果你解决了请告诉我。 @AlexanderZarovniy 问题出在 3rd-party 服务中,我们对 4.4 以下的 android 使用不同的方案(与 android 无关)【参考方案4】:

如果您的应用正在使用或愿意使用 Google Play 服务,您可以通过安装 Provider 在旧手机上使用更新的安全功能。它很容易安装,只有一行(加上异常处理等)。如果您还没有它,您还需要将 google play 服务添加到您的 gradle 文件中。 ProviderInstaller 包含在 -base 包中。

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

有关完整示例,请参阅来自 Google 的 "Updating Your Security Provider to Protect Against SSL Exploits"。

【讨论】:

注意:这是在主线程(UI 线程)上调用的同步调用。您可以在后台运行的 SyncAdapter 或 AsyncTask 中运行此调用以执行此更新尝试,或使用此调用的 installIfNeededAsync 版本进行调用以避免阻塞主线程 此外,即使在成功安装提供程序之后,您也需要在 [16-20] 之间的 Android 版本中发出 https 请求时显式启用 TLS 1.2/TLS 1.1。我在 KITKAT 设备上验证了这一点。 注意:这似乎无法解决 4.1.2 客户端的问题。 @frank 我设法让它工作的最低版本是 4.4.2。拦截套接字创建和调用setEnabledProtocols 似乎是那里唯一的解决方案。 这仍然适用于任何人吗?我注意到我的 4.4 设备不再加载 TLS 1.2 提供的内容。

以上是关于使用 Android WebViewClient 启用特定 SSL 协议的主要内容,如果未能解决你的问题,请参考以下文章

Android WebViewClient 回调调用过于频繁

HTTP状态码webview android, WebViewClient

在 Android WebViewClient 中获取空白页

Android - 在 AsyncTask 中设置 WebViewClient 会导致 NullPointerException

Android WebViewClient,有404时不会调用onErrorReceived

如何从不在 Android 的 API 26 上的 WebView 获取 WebviewClient 实例?