Reprinted: Android webview implements ssl two-way authentication under https

Solve the problem that the APP application security report WebView has no risk of HTTPS verification .

1. Overview
1. Introduction
Https is simply a secure version of Http. The Https protocol is a network protocol constructed by the SSL+Http protocol that can perform encrypted transmission and identity authentication. It is more secure than the Http protocol.

The security mentioned here depends on SSL. The functions of SSL are as follows:

a. Authenticate users and servers to ensure that data is sent to the correct client and server. (Certificate verification)
b. Encrypted data to prevent data from being stolen during transmission. (Encryption)
c. Maintain data integrity and ensure that data is not changed during transmission. (Digest Algorithm)
Https requires a handshake between the client and the server before transmitting data. Only after the handshake passes, there will be a data transmission process. This handshake is the first function of SSL mentioned above, authenticating the user and the server. This authentication method is implemented with a certificate.

The above is just a brief introduction to Https, and a more comprehensive and specific overview can be directed to Baidu.

2. The focus
of this article The focus of this article is to realize the two-way authentication of SSL certificates, that is, the client and the server shake hands.

Include:

a. Generate client and server certificates.
b. Build a server that supports Https.
c. Realize the two-way certificate authentication of webview.
2. Generate a certificate
This article uses a self-signed certificate. Why not use a certificate issued by an authority? Because they want money! ! ! Of course, it is best if the company has applied. The following generates a server-side self-signed certificate.

How to generate a certificate? There is a keytool tool in the JDK, which is very simple, as long as the JDK is installed, a certificate can be generated. Go directly to the command line:

keytool -genkey -alias zx_server -keyalg RSA -keystore D:\key\zx_server.jks -validity 3600 -storepass 123456

Use the above command to generate a server-side certificate store file zx_server.jks in the "key" folder of the D disk, and its key store password is: 123456

Next, use the zx_server.jks certificate library to generate a server-side certificate, which can be used by the client:

keytool -export -alias zx_server -file D:\key\zx_server.cer -keystore D:\key\zx_server.jks -storepass 123456

A server-side certificate zx_server.cer containing the public key is generated.

3. Download tomcat, use self-signed certificate to configure Https
to download a tomcat first, this article uses tomcat 7, address: http://tomcat.apache.org/download-70.cgi

After receiving the compressed package, find the tomcat/config/server.xml file and open it in text form.

Under the Servcie tag, add the following tags:

<Connector SSLEnabled="true" 
			acceptCount="100" 
			disableUploadTimeout="true" 
			enableLookups="true" 
			keystoreFile="D:/key/zx_server.jks" 
			keystorePass="123456" 
			maxSpareThreads="75" 
			maxThreads="200" 
			minSpareThreads="5" port="8443" 
			protocol="org.apache.coyote.http11.Http11NioProtocol" scheme="https" 
			secure="true" 
			sslProtocol="TLS"
			clientAuth="false" /> 

Note: keystoreFile is the path of the zx_server.jks file just generated (fill in your own path here), and the value of keystorePass is the keystore password: 123456.

Now a tomcat server that supports Https is configured, it's that simple. Find tomcat/bin/startup.bat, double-click to start the server directly.

After the startup is successful, open the browser and enter https://localhost:8443/ to see the warning that the certificate is not trusted. Ignore it and enter directly to see the default home page of tomcat.

Fourth, webview implements two-way authentication
This step is the focus of this article. Through the above steps, an https server has been set up, and now two-way authentication needs to be implemented, that is to say, the client will also have a "jks file", and the server must have a "cer file" corresponding to it, abstractly speaking, it is the client end to authenticate the server end.

The above steps have generated zx_server.jks and zx_server.cer files. According to the method of generating the above certificates, generate two client files named: zx_client.jks and zx_client.cer. Now configure the client certificate.

1. Configure the server
The configuration of the server is very simple. Add some attributes to the tag added when configuring tomcat in the third step above

<Connector SSLEnabled="true" 
			acceptCount="100" 
			disableUploadTimeout="true" 
			enableLookups="true" 
			keystoreFile="D:/key/zx_server.jks" 
			keystorePass="123456" 
			maxSpareThreads="75" 
			maxThreads="200" 
			minSpareThreads="5" port="8443" 
			protocol="org.apache.coyote.http11.Http11NioProtocol" scheme="https" 
			secure="true" 
			sslProtocol="TLS"
			clientAuth="true"
			truststoreFile="D:/key/zx_client.cer" /> 

The rest of the above label remains unchanged, only clientAuth is set to true at the end of the label, and an additional attribute truststoreFile is added. This attribute should be the zx_client.cer file we just generated, but an error will be reported when starting the server after adding it. Now we add the file zx_client.cer to a newly generated jks file:
keytool -import -alias D:\key\zx_client -file D:\key\zx_client.cer -keystore D:\key\zx_client_for_sever.jks

Now modify the Server.xml file and change the value of the truststoreFile attribute to the zx_client_for_sever.jks file just generated

<Connector //其它属性不变
			clientAuth="true"
			truststoreFile="D:/key/zx_client_for_sever.jks" /> 

At this time, restart tomcat, and use the browser to visit the address just now, and you will find that "localhost does not accept your login certificate, or your login certificate may have expired."

Now we can no longer access our server with a browser. Next, let's configure the Android side to achieve access.

2. Configure the app
to implement the two-way authentication of the webview https certificate, and talk about the general implementation ideas:

a. Perform trust authentication on the certificate in the code.
b. Rewrite the shouldInterceptRequest method of WebViewClient, intercept WebView's Request request, obtain the SocketFactory that HttpsUrlConnection sets SSL for, use this HttpsUrlConnection to intercept data, and then return a new WebResourceResponse to WebView.

Solve a problem before starting according to the above ideas. The Android platform can only recognize the certificate file in bks format. Now we need to convert the jks file.

How to convert? There are many ways, here is one, using portecle, download address: https://sourceforge.net/projects/portecle/?source=typ_redirect

After downloading, unzip the file, find the portecle.jar file, and double-click it to run it. The steps are basically, select the jks file to be converted -> enter the password -> select the format to convert -> save the file, the general steps are as follows:

a. Select the jks file

b. Enter password

c. Select the conversion format and enter the password again

d. It prompts that the conversion is successful, just save it directly.

The steps are very simple, but the general steps are still posted.

Now a zx_client.bks file is produced, and then find the zx_server.cer file produced above, and put these two files in the Android assets folder.

Then rewrite WebViewClient to introduce these two certificates, and paste the code directly.

package com.zx.webview_ssl;
 
import android.annotation.TargetApi;
import android.net.Uri;
import android.os.Build;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
 
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
 
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
 
public class SslWebViewClient extends WebViewClient {
    
    
 
    private SSLContext sslContext;
 
    public SslWebViewClient() {
    
    
        try {
    
    
            TrustManager[] trustManagers = prepareTrustManager(MyApplication.mContext.getResources().getAssets().open("zx_server.cer"));
            KeyManager[] keyManagers = prepareKeyManager(MyApplication.mContext.getResources().getAssets().open("zx_client.bks"), "123456");
            sslContext = SSLContext.getInstance("TLS");
            X509TrustManager trustManager = null;
            if (null != trustManagers){
    
    
                trustManager = new MyTrustManager(chooseTrustManager(trustManagers));
            }else {
    
    
                trustManager = new UnSafeTrustManager();
            }
            sslContext.init(keyManagers, new TrustManager[]{
    
    trustManager}, new SecureRandom());
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
    
    
            e.printStackTrace();
        } catch (KeyStoreException e) {
    
    
            e.printStackTrace();
        } catch (KeyManagementException e) {
    
    
            e.printStackTrace();
        }
    }
 
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
    
    
        return processRequest(Uri.parse(url));
    }
 
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
    
    
        return processRequest(request.getUrl());
    }
 
    private WebResourceResponse processRequest(Uri uri) {
    
    
        try {
    
    
            //设置连接
            URL url = new URL(uri.toString());
            HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
            //为request设置SSL Socket Factory
            urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
 
            urlConnection.setHostnameVerifier(new HostnameVerifier() {
    
    
                @Override
                public boolean verify(String hostname, SSLSession session) {
    
    
                    return true;
                }
            });
 
            //获取请求的内容、contentType、encoding
            InputStream inputStream = urlConnection.getInputStream();
            String contentType = urlConnection.getContentType();
            String encoding = urlConnection.getContentEncoding();
            if (null != contentType){
    
    
                String mimeType = contentType;
                if (contentType.contains(";")){
    
    
                    mimeType = contentType.split(";")[0].trim();
                }
                //返回新的response
                return new WebResourceResponse(mimeType, encoding, inputStream);
            }
 
        } catch (MalformedURLException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        return null;
    }
 
    private TrustManager[] prepareTrustManager(InputStream... certificates) {
    
    
        if (certificates == null || certificates.length <= 0){
    
    
            return null;
        }
        try {
    
    
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            int index = 0;
            for (InputStream certificate : certificates) {
    
    
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
                try {
    
    
                    if (certificate != null)
                        certificate.close();
                } catch (IOException e){
    
    
 
                }
            }
            TrustManagerFactory trustManagerFactory = null;
            trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            return trustManagers;
        } catch (NoSuchAlgorithmException e) {
    
    
            e.printStackTrace();
        } catch (CertificateException e) {
    
    
            e.printStackTrace();
        } catch (KeyStoreException e) {
    
    
            e.printStackTrace();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return null;
 
    }
 
    private KeyManager[] prepareKeyManager(InputStream bksFile, String password) {
    
    
        try {
    
    
            if (bksFile == null || password == null){
    
    
                return null;
            }
            KeyStore clientKeyStore = KeyStore.getInstance("BKS");
            clientKeyStore.load(bksFile, password.toCharArray());
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(clientKeyStore, password.toCharArray());
            return keyManagerFactory.getKeyManagers();
        } catch (KeyStoreException e) {
    
    
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
    
    
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
    
    
            e.printStackTrace();
        } catch (CertificateException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return null;
    }
 
    private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {
    
    
        for (TrustManager trustManager : trustManagers) {
    
    
            if (trustManager instanceof X509TrustManager) {
    
    
                return (X509TrustManager) trustManager;
            }
        }
        return null;
    }
 
    public static class MyTrustManager implements X509TrustManager{
    
    
        private X509TrustManager defaultTrustManager;
        private X509TrustManager localTrustManager;
 
        public MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException {
    
    
            TrustManagerFactory var4 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            var4.init((KeyStore) null);
            defaultTrustManager = chooseTrustManager(var4.getTrustManagers());
            this.localTrustManager = localTrustManager;
        }
 
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    
    
 
        }
 
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    
    
            try {
    
    
                defaultTrustManager.checkServerTrusted(chain, authType);
            } catch (CertificateException ce) {
    
    
                localTrustManager.checkServerTrusted(chain, authType);
            }
        }
 
        @Override
        public X509Certificate[] getAcceptedIssuers() {
    
    
            return new X509Certificate[0];
        }
    }
 
    public static class UnSafeTrustManager implements X509TrustManager {
    
    
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
    
    
        }
 
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
    
    
        }
 
        @Override
        public X509Certificate[] getAcceptedIssuers() {
    
    
            return new java.security.cert.X509Certificate[]{
    
    };
        }
    }
 
 
}

The code is quite long. Let me briefly explain that the core part is the prepareTrustManager and prepareKeyManager methods called by lines 41 and 42 respectively. These two methods initialize the zx_client.cer and zx_client.bks certificates respectively, and then execute the two The certificate establishes a connection, and then sets the SSLSocketFactory for HttpsUrlConnection on line 79. This SSLSocketFactory is obtained from the SSLContext initialized on line 50. The request at this time is safe, and finally returns a new response on line 98. In this way, the idea just mentioned above is basically realized, so as to realize the SSL two-way authentication of WebView.

Finally, let's take a look at the running effect on the mobile phone:

You can see that we have successfully accessed the home page of tomcat.

5. Finally,
only a very small number of applications require two-way authentication, such as some banking or financial apps. This is the end of this article, as long as you follow the above steps, you can realize the SSL two-way authentication of webview under https.

Source code: https://github.com/xunzzz/WebView_SSL

reference:

http://www.cnblogs.com/devinzhang/archive/2012/02/28/2371631.html
http://blog.csdn.net/kpioneer123/article/details/51491739
http://blog.csdn.net/lmj623565791/article/details/48129405

Guess you like

Origin blog.csdn.net/u013290250/article/details/121379479