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

本文介绍了一种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证书不定期更新的问题,你们有什么好的解决办法吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值