今年春节前夕客户反映在升级到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.crt
InputStream 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 CAs
String 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 KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
// Tell the URLConnection to use a SocketFactory from our SSLContext
URL 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服务器有不少问题和坑,小伙伴们如果碰到这种情况,最好先研究一下客户端的处理比较好。
本文探讨了Android7.0中SSL证书处理的变化及应对方法。重点介绍了自签名证书的应用场景及其在APP端的配置步骤,并讨论了可能出现的问题如证书过期与多服务器证书管理。

493

被折叠的 条评论
为什么被折叠?



