Android7.0的SSL自签名证书问题

来源:互联网 发布:linux 修改部分文件名 编辑:程序博客网 时间:2024/06/05 12:00

今年春节前夕客户反映在升级到Android7.0后APP不能通讯了。本人这个春节算是没过好了-_-!。
简单研究后发现是Android7.0对SSL证书的处理有了改进,更合理或者说更严格了。而原先的安卓前端使用的允许所有证书的写法失效了(本人没人发现有可以用的方法)。当然这个方法确实不推荐使用的,一旦这么做会产生很大的安全隐患。
其实官方的文档对SSL证书问题已经说明的非常清楚了,还是中文的。以下是链接
https://developer.android.com/training/articles/security-config.html#CustomTrust
https://developer.android.com/training/articles/security-ssl.html#HttpsExample
(需要翻墙)

首先Android系统自带了大量证书的,如果你有证书颁发机构 (CA)发放的证书,那么你的APP端不需要做什么特殊的事了。不过一般需要花钱才能买到这种证书。
第二就是采用自签名证书的情况,这时候就需要在APP端做一些事了。APP需要导入pem或crt为扩展名的公钥。一般这就是一些字符串,以BEGIN CERTIFICATE开头END CERTIFICATE结尾
比如这样:

-----BEGIN CERTIFICATE-----MIIDnDCCAoQ.....avwIvw==-----END CERTIFICATE-----

可以通过使用openssl做出来,具体做法就不赘述了,小伙伴们可以找度娘。
然后就是APP需要使用这个公钥给自己的https通讯加密,官方文档上已经有代码了,这里直接贴一下。

// Load CAs from an InputStream// (could be from a resource or ByteArrayInputStream or ...)CertificateFactory cf = CertificateFactory.getInstance("X.509");// From https://www.washington.edu/itconnect/security/ca/load-der.crtInputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt"));Certificate ca;try {    ca = cf.generateCertificate(caInput);    System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());} finally {    caInput.close();}// Create a KeyStore containing our trusted CAsString keyStoreType = KeyStore.getDefaultType();KeyStore keyStore = KeyStore.getInstance(keyStoreType);keyStore.load(null, null);keyStore.setCertificateEntry("ca", ca);// Create a TrustManager that trusts the CAs in our KeyStoreString tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);tmf.init(keyStore);// Create an SSLContext that uses our TrustManagerSSLContext context = SSLContext.getInstance("TLS");context.init(null, tmf.getTrustManagers(), null);// Tell the URLConnection to use a SocketFactory from our SSLContextURL url = new URL("https://certs.cac.washington.edu/CAtest/");HttpsURLConnection urlConnection =    (HttpsURLConnection)url.openConnection();urlConnection.setSSLSocketFactory(context.getSocketFactory());InputStream in = urlConnection.getInputStream();copyInputStreamToOutputStream(in, System.out);

注意几个坑
1.最后一句代码不能编译,其实你只需要给自己的HttpsURLConnection设置上SSLSocketFactory就行了。
2.HttpsURLConnection是HttpURLConnection的子类,打开https的url时才会返回HttpsURLConnection这点要注意。
3.证书一般会绑定url地址,可以通过证书信息看到
证书信息
如果和url不匹配会报错,可以自己改写一下HostnameVerifier的规则。当然这会降低安全性。

urlConnection.setHostnameVerifier(new HostnameVerifier() {    @Override     public boolean verify(String hostname, SSLSession session) {     //这里也可以直接返回true更简单        HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();        return hv.verify("www.xxxx.com", session);    }});

还有几个难题:
1.证书过期的问题。一旦过期,客户端的证书怎么更新是个大问题。本人这次使用了长有效期的证书规避这个问题了。
2.多个服务器多个证书的情况,客户端的处理就非常麻烦了。这次本人解决方法是使用nginx做负载均衡,统一使用一个证书。但是这个需要改变后台结构,需要慎重。

总结一下,使用自签名的HTTPS服务器有不少问题和坑,小伙伴们如果碰到这种情况,最好先研究一下客户端的处理比较好

0 0
原创粉丝点击