首页 > 解决方案 > 启用 TLS 1.2 的 Android Web 视图

问题描述

我正在开发一个 android 应用程序,它只能在 Android 4.2.2 设备上运行。

该应用程序具有 Web 视图。在该 Web 视图中,我正在加载一个托管在 Firebase 服务器上的网站。

  1. 该网站使用 HTTPS 保护
  2. 该应用程序使用 WebViewClient 与服务器建立 SSL 连接。
  3. 与此站点的连接使用 TLS 1.2、ECDHE_RSA with X25519 和 AES_128_GCM 进行加密和身份验证。


  1. 我添加了 TLSScoketFactory 来启用 TLS1.2。默认情况下,在 4.2.2 版本中未启用。
  2. 我正在使用 okhttp 客户端进行请求。
public class TLSSocketFactory extends SSLSocketFactory {

    private static final String TAG = "TLSSocketFactory";

    private SSLSocketFactory internalSSLSocketFactory;

    public TLSSocketFactory() {
        internalSSLSocketFactory = getSslContext().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)  && isTLSServerEnabled((SSLSocket) socket) ) {
            ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.2"});
        }
        return socket;
    }


    public static X509TrustManager getTrustManager() {
        return new X509TrustManager() {
            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return new java.security.cert.X509Certificate[] {};
            }

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

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

    public static SSLContext getSslContext() {
        try {
            SSLContext sslContext =  SSLContext.getInstance("TLS");
            sslContext.init(null, null, null);
            return sslContext;
        } catch (Exception ex) {
            Log.e(TAG, "Problem while getting SslContext", ex);
            throw new RuntimeException(ex);
        }
    }
}
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private static final String HTTP = "http://";
    private static final String HTTPS = "https://";
    private static final String CSS_FILE = ".css";
    private static final String JS_FILE = ".js";
    private static final String CSS_DIR = "/css/";
    private static final String JS_DIR = "/js/";

    private WebView webView;
    private ImageView button;
    private EditText urlView;
    private ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        progressBar = (ProgressBar) findViewById(R.id.progress_bar);
        progressBar.getProgressDrawable().setColorFilter(getResources().getColor(R.color.colorAccent), android.graphics.PorterDuff.Mode.SRC_IN);

        urlView = (EditText) findViewById(R.id.url);

        webView = (WebView) findViewById(R.id.web_view);
        WebSettings settings = webView.getSettings();
        settings.setJavaScriptEnabled(true);
        settings.setDomStorageEnabled(true);
        webView.setWebChromeClient(buildWebChromeClient());
        webView.setWebViewClient(buildClient());

        webView.post(new Runnable() {
            @Override
            public void run() {
                webView.loadUrl("https://sample-project.firebaseapp.com");
            }
        });

        button = (ImageView) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onButtonClick();
            }
        });
    }



    private WebViewClient buildClient() {
        return new WebViewClient() {

           private OkHttpClient okHttp = new OkHttpClient.Builder().sslSocketFactory(new TLSSocketFactory(), TLSSocketFactory.getTrustManager()).build();



            @Override
            public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
                Log.e(TAG, "URL -> " + url);

             /*   if (url.contains(CSS_FILE) || url.contains(JS_FILE) || url.contains(CSS_DIR) || url.contains(JS_DIR)) {
                    Log.e(TAG, "Css or js -> default interceptor");
                    return super.shouldInterceptRequest(view, url);
                } else {*/
                    Request okHttpRequest = new Request.Builder().url(url).header("User-Agent", "OkHttp Example").build();
                    try {
                        Response response = okHttp.newCall(okHttpRequest).execute();
                        return new WebResourceResponse("text/html", "UTF-8", response.body().byteStream());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return null;
           //     }
            }

            public void onPageFinished(WebView view, String url) {
                Log.i(TAG, "Finished loading URL: " + url);
                super.onPageFinished(view, url);
             //   webView.loadUrl(url);
            }

            public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
              //  MessageDialogFragment.getInstance(R.string.information, getString(R.string.oh_no) + description).show(getSupportFragmentManager(), "MessageDialogFragment");
                Log.i(TAG, "failing URL: " + failingUrl);
            }

            @Override
            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                handler.proceed(); // Ignore SSL certificate errors
            }
        };
    }

    private WebChromeClient buildWebChromeClient() {
        return new WebChromeClient() {
            public void onProgressChanged(WebView view, int progress) {
                if (progress < 100 && progressBar.getVisibility() == ProgressBar.GONE) {
                    progressBar.setVisibility(ProgressBar.VISIBLE);
                }

                progressBar.setProgress(progress);
                if (progress == 100) {
                    progressBar.setVisibility(ProgressBar.GONE);
                }
            }

            @Override
            public boolean onConsoleMessage(ConsoleMessage m) {
                switch (m.messageLevel()) {
                    case LOG:
                        Log.i(TAG, String.format("%s (%s:%d)", m.message(), m.sourceId(), m.lineNumber()));
                        return true;
                    case TIP:
                        Log.v(TAG, String.format("%s (%s:%d)", m.message(), m.sourceId(), m.lineNumber()));
                        return true;
                    case DEBUG:
                        Log.d(TAG, String.format("%s (%s:%d)", m.message(), m.sourceId(), m.lineNumber()));
                        return true;
                    case WARNING:
                        Log.w(TAG, String.format("%s (%s:%d)", m.message(), m.sourceId(), m.lineNumber()));
                        return true;
                    case ERROR:
                        Log.e(TAG, String.format("%s (%s:%d)", m.message(), m.sourceId(), m.lineNumber()));
                        return true;
                }
                return false;
            }

        };
    }

    private void onButtonClick() {
        KeyboardManager.hideKeyboard(this, getCurrentFocus());

        if(NetworkManager.isNetworkEnable(this)) {
            String url = urlView.getText().toString();
            if (StringUtils.isNotBlank(url)) {
                if(!(url.startsWith(HTTP) || url.startsWith(HTTPS))){
               //     url = HTTP + url;
                }
              //  webView.loadUrl(url);
            } else {
                MessageDialogFragment.getInstance(R.string.information, getString(R.string.wrong_url)).show(getSupportFragmentManager(), "MessageDialogFragment");
            }
        } else {
            MessageDialogFragment.getInstance(R.string.information, getString(R.string.no_network)).show(getSupportFragmentManager(), "MessageDialogFragment");
        }
    }

}

当我尝试在 WebView 中加载网站 URL 时遇到的问题是

E/chromium: external/chromium/net/socket/ssl_client_socket_openssl.cc:792: [0102/102633:ERROR:ssl_client_socket_openssl.cc(792)] 握手失败;返回 -1,SSL 错误代码 1,net_error -107

标签: androidsslandroid-webviewtls1.2sslhandshakeexception

解决方案


推荐阅读