Android合规问题引起的https证书校验

Posted 汤米粥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android合规问题引起的https证书校验相关的知识,希望对你有一定的参考价值。

     //1、设置httpsURLConnection.setSSLSocketFactory

    public static HttpURLConnection getHttpURLConnection(URL url) throws IOException 
        HttpURLConnection conn = null;
        if (url.toString().startsWith("https")) 
            final HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
            httpsURLConnection.setHostnameVerifier(HOSTNAME_VERIFIER);
            try 
                httpsURLConnection.setSSLSocketFactory(getSSLSocketFactory(null));
             catch (NoSuchAlgorithmException | KeyManagementException e) 
                e.printStackTrace();
            
            conn = httpsURLConnection;
         else 
            conn = (HttpURLConnection) url.openConnection();
        
        return conn;
    
    /**
     * '
     * 暂时解决合规问题
     * <p>
     * 使用服务器端证书,只信任指定证书。
     * 但是由于服务器端证书3个月一变,如果过了12.10后为了防止访问不了,信任所有证书。
     *
     * @param context
     * @return
     * @throws NoSuchAlgorithmException
     * @throws KeyManagementException
     */
    public static SSLSocketFactory getSSLSocketFactory(Context context) throws NoSuchAlgorithmException, KeyManagementException 
        X509TrustManager xtm;

        if (System.currentTimeMillis() < 1670662401000l && context != null) 

            if (SSLConfigUtils.sslKey == null) 
                xtm = createTrustCustomTrustManager(getInputStreamFromAsset(context));
//                Toast.makeText(context,"证书校验1",0).show();
             else 
                xtm = createTrustCustomTrustManager(new ByteArrayInputStream(SSLConfigUtils.sslKey.getBytes()));
//                Log.e("xxx-我的证书",SSLConfigUtils.sslKey);
//                Toast.makeText(context,"证书校验2",0).show();
            
         else 
            xtm = getX509TrustManager();
//            Toast.makeText(context,"证书不校验",0).show();
        
        SSLContext sslContext = null;
        sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, new TrustManager[]xtm, new SecureRandom());
        return sslContext.getSocketFactory();
    

 /**
     * 创建只信任指定证书的TrustManager
     *
     * @param inputStream:证书输入流
     * @return
     */
    private static X509TrustManager createTrustCustomTrustManager(InputStream inputStream) 
        try 
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);

            Certificate certificate = certificateFactory.generateCertificate(inputStream);
            //将证书放入keystore中
            String certificateAlias = "ca";
            keyStore.setCertificateEntry(certificateAlias, certificate);
            if (inputStream != null) 
                inputStream.close();
            

            TrustManagerFactory trustManagerFactory = TrustManagerFactory.
                    getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) 
                throw new IllegalStateException("Unexpected default trust managers:"
                        + Arrays.toString(trustManagers));
            
            return (X509TrustManager) trustManagers[0];
         catch (Exception e) 
            e.printStackTrace();
        
        return null;
    

    /**
     * 读取放在assets里的证书(服务器端配置的证书公钥)
     *
     * @param context
     * @return
     */
    private static InputStream getInputStreamFromAsset(Context context) 
        InputStream inputStream = null;
        try 
            inputStream = context.getAssets().open("ssl.pem");
         catch (IOException e) 
            e.printStackTrace();
        
        return inputStream;
    
    /**
     * 创建信任所有证书的TrustManager
     */
    @NonNull
    public static X509TrustManager getX509TrustManager() 
        return new X509TrustManager() 
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) 
            

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) 
            

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

import android.util.Log;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class SSLConfigUtils 

    private InputStream inputStream;

    public static  String sslKey;


    public static InputStream doGetSSL() 

        BufferedReader reader = null;
        String bookJsonString = null;
        InputStream inputStream = null;

        Log.e("xxx", "获取证书");
        try 
            //1.HttpURLConnection建立连接
            HttpURLConnection httpURLConnection = null;
            String url = "https://xxx.com/xxx/ssl.pem";
            URL requestUrl = null;
            try 
                requestUrl = new URL(url);

                httpURLConnection = (HttpURLConnection) requestUrl.openConnection();//打开连接
                httpURLConnection.setRequestMethod("GET");//两种方法GET/POST
                httpURLConnection.setConnectTimeout(5000);//设置超时连接时间
                httpURLConnection.connect();


                //2.InputStream获取二进制流
                inputStream = httpURLConnection.getInputStream();

                //3.InputStreamReader将二进制流进行包装成BufferedReader
                reader = new BufferedReader(new InputStreamReader(inputStream));

                //4.从BufferedReader中读取String字符串,用StringBulider接收
                StringBuilder bulider = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) 
                    bulider.append(line);
                    bulider.append("\\n");
                
                //5.StringBulider将字符串进行拼接
                bookJsonString = bulider.toString();

//                Log.e("xxx", bookJsonString);

                sslKey = bookJsonString;



             catch (Exception e) 
                e.printStackTrace();
            
         catch (Exception e) 
            e.printStackTrace();
        
        return inputStream;
    


现在来讲解一下上述代码解决了一个什么事情 :

1.首先网信办、应用市场要求不能用明文传输,所以使用了https传输。

2.使用https发现网络请求不了,因为https要求证书通过验证,为了接口能正常访问,于是设置了X509TrustManager 信任所有证书。

事情到这儿好像是解决了,但相关部门又提出来了,X509TrustManager这种信任所有证书存在安全隐患,不合规。使用charles抓包亲测(将charles证书导出安装到手机上)的确能抓取到https传输的明文信息。

3.于是改成只信任指定ssl证书。具体做法是:在app端上assets目录里存放服务器端给的ssl证书 *.pem(公钥),然后通过getInputStreamFromAsset的方式读取,接着调用   xtm = createTrustCustomTrustManager(getInputStreamFromAsset(context));设置只信任服务器端给的指定ssl证书。通过添加上述操作之后,再使用charles抓包(将charles证书导出安装到手机上),发现抓取不到网络请求。到此第2步的问题解决。

某天发现app访问不了网络,原来是服务器端的ssl证书是在国内某机构申请的免费证书,3个月就会更新一次。服务器端证书更新了,但存放在app端的还是旧的证书,故会出现访问不了网络的问题。于是出现了接下来的操作。

4.服务器端证书发生变化时及时放到网络上,通过接口(见上面代码SSLConfigUtils )下载到本地,然后在接口请求中设置信任下载的证书。

虽然这样把证书放到网络上不安全,但能解决相关部门的检查,就这样吧。 针对这种服务器端https证书不定期更新的问题,你们有什么好的解决办法吗?

以上是关于Android合规问题引起的https证书校验的主要内容,如果未能解决你的问题,请参考以下文章

android中使用https是否对服务证书合法性校验的新的体会

Android APP之WebView如何校验SSL证书

Android APP之WebView如何校验SSL证书

移动端HTTPS证书校验过程是怎样的

Https 证书校验加密机制

HTTPS证书校验