Android WebView https white screen, Http and Https mixed problems, certificate configuration and use

preface

In the original project, some interfaces are h5 pages displayed by webview, which always use the http address. However, in some cases, the user dns is hijacked, and some advertising content appears on the page, or the page is a white screen. To sum up, the page content is hijacked and modified because of the use of http, and the modified content either has more advertisements, Or it is modified so that it cannot be loaded, so the https address needs to be loaded in the project. At the same time, after it is modified to https, those users with problems can also display h5 page content normally.

White screen after enabling https (certificate error)

However, after the new version was launched, customers reported that oppo(8.1), vivo and other users reported that the page in the app was blank, and the verification of oppo and vivo by the company was no problem. Finally, the little partner reappears with the simulator and sees the error log:

The log here is typed in the re implementation of WebChromeClient (the code has been modified):

  @Override
            public void onReceivedSslError(WebView webView, SslErrorHandler sslErrorHandler, SslError sslError) {
                GenseeLog.e(TAG,"onReceivedSslError sslErrorHandler = [" + sslErrorHandler + "], sslError = [" + sslError + "]");
                _onReceivedError();
                super.onReceivedSslError(webView, sslErrorHandler, sslError);
            }

Modification processing

It was learned that the related errors were answered in the onReceivedSslError re implementation of WebChromeClient, and sslErrorHandler.process() was called in onReceivedSslError. Just ignore the certificate problem.

     public void onReceivedSslError(WebView webView, SslErrorHandler sslErrorHandler, SslError sslError) {
                GenseeLog.e(TAG,"onReceivedSslError sslErrorHandler = [" + sslErrorHandler + "], sslError = [" + sslError.getPrimaryError() + "]");
                _onReceivedError();
                sslErrorHandler.proceed();
//                super.onReceivedSslError(webView, sslErrorHandler, sslError);
            }

Note here: do not call super.onreceivedsslerror (WebView, sslerrorhandler, sslerror). The implementation of super is sslErrorHandler.cancel();, Terminate access.

   public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
        handler.cancel();
    }

Note: the text is handled by ignoring the certificate, but it doesn't work in terms of substantive security. Confirm that our certificate is correct. Only a few oppo (system 8.1) and vivo users have problems. At present, they have not been verified by the real machine. It is speculated that the problem is still the download of the system certificate. Thank old fellow for your message.
The correct recommended practice is to verify the certificate:

webView.setWebViewClient(new  WebViewClient() {
        override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
               String msg;
              switch(error.getPrimaryError()) {
                       case: SslError.SSL_DATE_INVALID
                       msg = "The certificate date is invalid"
                       break;
                       case: SslError.SSL_EXPIRED
                       msg =  "The certificate has expired."
                        break;
                       case: SslError.SSL_IDMISMATCH 
                       msg = "Host names do not match."
                        break;
                       case: SslError.SSL_INVALID 
                       msg = "A general error occurred"
                        break;
                        case: SslError.SSL_MAX_ERROR
                       msg = "Different SSL Number of errors."
                        break;
                        case: SslError.SSL_NOTYETVALID
                       msg =  "The certificate is not yet valid."
                        break;
                        case: SslError.SSL_UNTRUSTED
                       msg =  "The certification authority is not trusted." // The custom certificate will go to this branch
                         break;
                         default:
                         
                       msg ="SSL Certificate error,Error code:"+ error.getPrimaryError();
                }
                Log.i("SSL Error:" + msg)

           if (error.getPrimaryError() == SslError.SSL_UNTRUSTED) {
           // If the certification authority is not trusted, we need to determine whether it is our own custom certificate. If so, ignore this error

           CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
           X509Certificate certificate = certificateFactory.generateCertificate(resources.openRawResource(R.raw.xxx)) ;
           Field mX509CertificateFiled = SslCertificate.getClass().getDeclaredField("mX509Certificate");
           mX509CertificateFiled .setAccessible( true);
           X509Certificate mX509Certificate = mX509CertificateFiled.get(error.certificate());
                    val certificates = HandshakeCertificates.Builder()
                        .addTrustedCertificate(certificate) // Trust the specified custom certificate
                        .addPlatformTrustedCertificates()  // Trust the pre installed certificate of the system. If you do not trust the system certificate, for example, when accessing https://m.baidu.com An error will occur
                        .build()

                    try {
                        certificates.trustManager.checkServerTrusted(new X509Certificate []{mX509Certificate}), "RSA")
                        Log.i("This is our custom certificate")
                        handler.proceed()
                    } catch (e: java.lang.Exception) {
                        Log.e(e, "Illegal certificate")
                        handle.cancel()
                    }
                }
            } else {
                super.onReceivedSslError(view, handler, error)
            }
        }
});

Mixed problem of Http and Https in WebView

A page is requested through http, but there are https resources in the page. The latter page is accessed through https, but there are http resources in the page. Generally, the latter has problems, and the former may also have problems at present.

Treatment method

Before the webview loads the page, set the loading mode to MIXED_CONTENT_ALWAYS_ALLOW

       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
       }

Before Android 5.0, the system used mixed by default_ CONTENT_ ALWAYS_ Allow mode, that is, WebView is always allowed to load Https and Http at the same time; Starting from Android 5.0, mixed is used by default_ CONTENT_ NEVER_ Allow mode, that is, WebView is always not allowed to load Https and Http at the same time.

If they are all home pages, this problem should be handled from the source. Don't use a mixed way.

The suggestion given on the official website is to use mixed for security reasons_ CONTENT_ NEVER_ Allow mode, but in the actual reference, when our server has been upgraded to Https, but the resources of some pages are third-party, we may not require all third parties to upgrade to Https, so we can only use code to set the loading mode to mixed according to the system version_ CONTENT_ ALWAYS_ ALLOW.

Several content loading modes of Webview

Starting from Android 5.0, when a secure site (https) loads a non secure site (http), you need to configure the mixed mode of Webview loading content. There are three modes in total:

  • MIXED_CONTENT_NEVER_ALLOW: Webview does not allow a secure site (https) to load non secure site content (http). For example, the picture of https web page content is an http link. It is strongly recommended that App use this mode because it is safer.
  • MIXED_CONTENT_ALWAYS_ALLOW: in this mode, WebView can load non secure site content (Http) in a secure site (Https), which is the most insecure operation mode of WebView. Do not use this mode as much as possible.
  • MIXED_CONTENT_COMPATIBILITY_MODE: in this mode, when mixed content is involved, WebView will try to be compatible with the style of the latest Web browser. Some insecure content (Http) can be loaded on a secure site (Https), while other types of content will be blocked. Whether the types of these contents are allowed to be loaded or blocked may change with different versions, and there is no clear definition. This mode is mainly used to control the rendering of content in the App, but you want to run in a safe environment.

Certificate configuration or processing

If you need to configure a certificate,
Create an XML file directory under the res directory, and then create a network_security_config.xml (name optional), then write some https configurations in this file, and then put network_security_config is configured in the Application node attribute of Anroidmanifest, as follows:

<application
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:networkSecurityConfig="@xml/network_security_config">
        
</application>

According to the specification, our xxx.crt certificate file needs to be placed under the res/raw / directory. Then configure to network_security_config file. There are two ways to configure certificates, as follows:

  1. base-config
    Indicates that all domain name resources accessed by the application trust the xxx certificate specified by us
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config>
        <trust-anchors>
            <certificates src="@raw/xxx" />
        </trust-anchors>
    </base-config>
</network-security-config>

  1. domain-config
    Using domain config restricts baidu.com or its sub domain name from trusting the xxx certificate specified by us
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">gensee.com</domain>
        <trust-anchors>
            <certificates src="@raw/xxx"/>
        </trust-anchors>
    </domain-config>
</network-security-config>

  1. Specifies that the preinstalled certificate is trusted
    To configure what certificates we want to trust, directly use @ raw/xxx to configure custom certificates for trust. If you want to specify the trust of pre installed certificates, you need to specify them separately. Pre installed certificates have two types: system and user, as follows:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config>
        <trust-anchors>
            <certificates src="@raw/custom_ca" /> <!--Trust custom CA certificate-->
            <certificates src="system" /> <!--Trust system pre installed CA certificate-->
            <certificates src="user" />   <!--Trusted user installed CA certificate-->
        </trust-anchors>
    </base-config>
</network-security-config>

https requested certificate

Request from okhttp:

val builder = OkHttpClient.Builder()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
	val certificateFactory: CertificateFactory = CertificateFactory.getInstance("X.509")
	val certificate = certificateFactory.generateCertificate(resources.openRawResource(R.raw.xxx)) as X509Certificate
	val certificates = HandshakeCertificates.Builder()
                    .addTrustedCertificate(certificate) // Trust the specified custom certificate
                     .addPlatformTrustedCertificates()  // Trust the pre installed certificate of the system. If you do not trust the system certificate, for example, when accessing https://m.baidu.com An error will occur
                    .build()
	builder.sslSocketFactory(certificates.sslSocketFactory(), certificates.trustManager)
}
val okHttpClient = builder.build()

HttpsURLConnection

    // Load CAs from an InputStream
    // (could be from a resource or ByteArrayInputStream or ...)
    val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
    // From https://www.washington.edu/itconnect/security/ca/load-der.crt
    val caInput: InputStream = BufferedInputStream(FileInputStream("load-der.crt"))
    val ca: X509Certificate = caInput.use {
        cf.generateCertificate(it) as X509Certificate
    }
    System.out.println("ca=" + ca.subjectDN)

    // Create a KeyStore containing our trusted CAs
    val keyStoreType = KeyStore.getDefaultType()
    val keyStore = KeyStore.getInstance(keyStoreType).apply {
        load(null, null)
        setCertificateEntry("ca", ca)
    }

    // Create a TrustManager that trusts the CAs inputStream our KeyStore
    val tmfAlgorithm: String = TrustManagerFactory.getDefaultAlgorithm()
    val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
        init(keyStore)
    }

    // Create an SSLContext that uses our TrustManager
    val context: SSLContext = SSLContext.getInstance("TLS").apply {
        init(null, tmf.trustManagers, null)
    }

    // Tell the URLConnection to use a SocketFactory from our SSLContext
    val url = URL("https://certs.cac.washington.edu/CAtest/")
    val urlConnection = url.openConnection() as HttpsURLConnection
    urlConnection.sslSocketFactory = context.socketFactory
    val inputStream: InputStream = urlConnection.inputStream
    copyInputStreamToOutputStream(inputStream, System.out)
    

Ignore certificate

/** Get an SSLSocketFactory */
val sSLSocketFactory: SSLSocketFactory
    get() = try {
        val sslContext = SSLContext.getInstance("SSL")
        sslContext.init(null, arrayOf(trustManager), SecureRandom())
        sslContext.socketFactory
    } catch (e: Exception) {
        throw RuntimeException(e)
    }

/** Get an X509TrustManager that ignores the certificate */
val trustManager: X509TrustManager
    get() = object : X509TrustManager {
            override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) { }	
        override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) { }	
        override fun getAcceptedIssuers(): Array<X509Certificate> { return arrayOf() }
    }

Set these two objects to okhttp or HttpsURLConnection to complete certificate ignoring.

https://blog.csdn.net/android_cai_niao/article/details/108065766
https://blog.csdn.net/luofen521/article/details/51783914

Tags: Java Android Design Pattern http https

Posted on Tue, 28 Sep 2021 22:48:59 -0400 by angel_cowgirl