okhttp implements https access and supports https access in Android 4.X system

Keywords: Java SSL Android socket

Last year, we used Retrofit+OkHttp to make network requests when we reconstructed our company's project. Because our company's network requests are accessed by https, we began to solve the problem of Https network transmission after encapsulating the framework of Retrofit+OkHttp network requests suitable for our project. At first, I read a lot of blog posts and learned from the methods of these blog posts. As a result, none of them can achieve Https access. Until you see Hongyang uuuuuuuuuuuuu An improved okHttp encapsulation library for Android When I wrote this blog, I was curious about how he solved the problem of okhttp accessing https, and then I was there. okhttputils Home page, found HttpsUtils This method, I have to say that God's code is strong, after using this tool, the request really succeeded.

Later, I added a host name checking method to this tool:

/**
 * Host name checking method
 */
public static HostnameVerifier getHostnameVerifier() {
    return new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return hostname.equalsIgnoreCase(session.getPeerHost());
        }
    };
}

So the use of this tool becomes like this:

    HttpsUtil.SSLParams sslParams = HttpsUtil.getSslSocketFactory(Utils.getContext(), new int[0], R.raw.xxxx, "password");
    okHttpClient = new OkHttpClient.Builder()
            .connectTimeout(10000L, TimeUnit.MILLISECONDS)
            .sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)
            .hostnameVerifier(HttpsUtil.getHostnameVerifier())
            .addInterceptor(new LoggerInterceptor(null, true))
            .build();

However, one day a buddy ran our APP on Android 4.3 system and found that the network request failed and reported the following error:

System.err: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x5ff1c438: Failure in SSL library, usually a protocol error
System.err: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:741 0x5cb66770:0x00000000)

As soon as I saw this error, I knew that our Https Utils didn't work because I often encountered handshake aborted when I didn't solve the Https network request, so I started looking for the reason why HTTPS access didn't work on Android 4.3, and found this blog post: Solutions for Android 4.x System Supporting TLS1.2 After half a day, it turned out that Android 4.X system did not support TLSv1.1 and TLSv1.2 protocols, so I quickly added the solution of this blog to my HttpsUtils and made Android system judgment, so the tool finally became the following:

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.support.annotation.RawRes;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

/**
 * HttpsUtils From Hongyang: https://github.com/hongyang Android/okhttputils;
 * The host name verification method getHostname Verifier () was added.
 * Other references are: http://android.jobbole.com/83787/;
 *
 * Android 4.X Support for TLS 1.1 and TLS 1.2 refers to http://blog.csdn.net/joye123/article/details/53888252.
 */
public class HttpsUtil {

    /**
     * Packaged SSL(Secure Socket Layer) parameter class
     */
    public static class SSLParams {
        public SSLSocketFactory sSLSocketFactory;
        public X509TrustManager trustManager;
    }

    /**
     * @param context        context
     * @param certificatesId "XXX.cer" File (file location res/raw/XXX.cer)
     * @param bksFileId      "XXX.bks"File (file location res/raw/XXX.bks)
     * @param password       The certificate's password.
     * @return SSLParams
     */
    public static SSLParams getSslSocketFactory(Context context, @RawRes int[] certificatesId, @RawRes int bksFileId, String password) {
        if (context == null) {
            throw new NullPointerException("context == null");
        }
        SSLParams sslParams = new SSLParams();
        try {
            TrustManager[] trustManagers = prepareTrustManager(context, certificatesId);
            KeyManager[] keyManagers = prepareKeyManager(context, bksFileId, password);

            //Create an SSLContext object of TLS type, which uses our TrustManager
            SSLContext sslContext = SSLContext.getInstance("TLS");

            X509TrustManager x509TrustManager;
            if (trustManagers != null) {
                x509TrustManager = new MyTrustManager(chooseTrustManager(trustManagers));
            } else {
                x509TrustManager = new UnSafeTrustManager();
            }
            //Initialize the SSLContext with the trustManagers obtained above, so that the sslContext trusts the certificates in the keyStore
            sslContext.init(keyManagers, new TrustManager[]{x509TrustManager}, null);

            //Getting the SSLSocketFactory object through sslContext
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                /*Android 4.X Support for TLS 1.1 and TLS 1.2*/
                sslParams.sSLSocketFactory = new Tls12SocketFactory(sslContext.getSocketFactory());
                sslParams.trustManager = x509TrustManager;
                return sslParams;
            }

            sslParams.sSLSocketFactory = sslContext.getSocketFactory();
            sslParams.trustManager = x509TrustManager;
            return sslParams;
        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
            throw new AssertionError(e);
        }
    }


    /**
     * Host name checking method
     */
    public static HostnameVerifier getHostnameVerifier() {
        return new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return hostname.equalsIgnoreCase(session.getPeerHost());
            }
        };
    }

    private static TrustManager[] prepareTrustManager(Context context, int[] certificatesId) {
        if (certificatesId == null || certificatesId.length <= 0) {
            return null;
        }

        try {
            //Create a Certification Factory in X.509 format
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            // Create a default type of KeyStore to store our trusted certificates
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            int index = 0;
            for (int certificateId : certificatesId) {
                //The flow of obtaining certificates from local resources
                InputStream cerInputStream = context.getResources().openRawResource(certificateId);
                String certificateAlias = Integer.toString(index++);

                //Certificate is java.security.cert.Certificate, not other Certificate
                //Certificate Factory Generates Certificate Based on the Stream of Certificate File
                Certificate certificate = certificateFactory.generateCertificate(cerInputStream);
                //Place certificate as a trusted certificate in keyStore
                keyStore.setCertificateEntry(certificateAlias, certificate);
                try {
                    if (cerInputStream != null)
                        cerInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            //TrustManagerFactory is used to generate TrustManager, and a default type TrustManagerFactory is created here.
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            //Initialize TrustManagerFactory with our previous keyStore instance so that trustManagerFactory trusts certificates in keyStore
            trustManagerFactory.init(keyStore);
            return trustManagerFactory.getTrustManagers();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static KeyManager[] prepareKeyManager(Context context, @RawRes int bksFileId, String password) {

    try {
            KeyStore clientKeyStore = KeyStore.getInstance("BKS");
            clientKeyStore.load(context.getResources().openRawResource(bksFileId), password.toCharArray());
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(clientKeyStore, password.toCharArray());
            return keyManagerFactory.getKeyManagers();

        } catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | IOException e) {
            e.printStackTrace();
        }
    return null;
    }


    private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {
        for (TrustManager trustManager : trustManagers) {
            if (trustManager instanceof X509TrustManager) {
                return (X509TrustManager) trustManager;
            }
        }
        return null;
    }


    /**
     * The client does not check the certificate.
     * There are many security vulnerabilities in the way that clients do not authenticate certificates.
     */
    private static class UnSafeTrustManager implements X509TrustManager {

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

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

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
        }
    }

    private static class MyTrustManager implements X509TrustManager {
        private X509TrustManager defaultTrustManager;
        private X509TrustManager localTrustManager;

        private MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException {
            //TrustManagerFactory is used to generate TrustManager, creating a default type of TrustManagerFactory
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init((KeyStore) null);
            defaultTrustManager = chooseTrustManager(trustManagerFactory.getTrustManagers());
            this.localTrustManager = localTrustManager;
        }


        @SuppressLint("TrustAllX509TrustManager")
        @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];
        }
    }


     /**
     * Self-implementation of SSL SocketFactory and Android 4.X support for TLSv1.1 and TLSv1.2
     */
    private static class Tls12SocketFactory extends SSLSocketFactory {

        private static final String[] TLS_SUPPORT_VERSION = {"TLSv1.1", "TLSv1.2"};

        final SSLSocketFactory delegate;

        private Tls12SocketFactory(SSLSocketFactory base) {
            this.delegate = base;
        }

        @Override
        public String[] getDefaultCipherSuites() {
            return delegate.getDefaultCipherSuites();
        }

        @Override
        public String[] getSupportedCipherSuites() {
            return delegate.getSupportedCipherSuites();
        }

        @Override
        public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
            return patch(delegate.createSocket(s, host, port, autoClose));
        }

        @Override
        public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
            return patch(delegate.createSocket(host, port));
        }

        @Override
        public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
            return patch(delegate.createSocket(host, port, localHost, localPort));
        }

        @Override
        public Socket createSocket(InetAddress host, int port) throws IOException {
            return patch(delegate.createSocket(host, port));
        }

        @Override
        public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
            return patch(delegate.createSocket(address, port, localAddress, localPort));
        }

        private Socket patch(Socket s) {
            //Proxy SSL SocketFactory sets the available TLS version of Socket when creating a Socket connection.
            if (s instanceof SSLSocket) {
                ((SSLSocket) s).setEnabledProtocols(TLS_SUPPORT_VERSION);
            }
            return s;
        }
    }
}

Of course, because the internal logic is only adjusted, the use of this tool remains unchanged:

    HttpsUtil.SSLParams sslParams = HttpsUtil.getSslSocketFactory(Utils.getContext(), new int[0], R.raw.xxxx, "password");
    okHttpClient = new OkHttpClient.Builder()
            .connectTimeout(10000L, TimeUnit.MILLISECONDS)
            .sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)
            .hostnameVerifier(HttpsUtil.getHostnameVerifier())
            .addInterceptor(new LoggerInterceptor(null, true))
            .build();

Finally, please click here for the HttpsUtil source code. HttpsUtil source code If you think it's helpful to you, please order a star.

And this is what I wrote. Detailed Implementation Scheme of Android Project Componentization Welcome your comments.

Posted by michelledebeer on Mon, 17 Dec 2018 10:51:03 -0800