流程图
Android https证书校验简化流程
SSL/TLS握手
思路
要防止攻击者拿到用户信息就必须从以下方向着手:
1、防抓包
2、校验SSL证书
3、加密隐私数据
加密数据需要后端配合较为麻烦,而且因为一直以来的不规范操作,要想大面积的加密隐私数据几乎不可能。
所以就只能从1、2两点着手
防抓包
目前主流两种抓包方式:代理、VPN
代理:在应用层(http),较为容易防范
VPN:在网络层(socket)
防代理
利用OKHttp
的api
OkHttpClient.Builder().proxy(Proxy.NO_PROXY).build()
此举只能防止代理抓包(Charles
、Fiddler
),VPN抓包不能防(Wireshark
)
防VPN
初步的方案是在拦截器中判断网络连接是否包含VPN连接方式,如下
ConnectivityManager.getNetworkCapabilities()
如果包含,则抛异常,但是考虑可能会造成性能损耗和耗时故没有采用(没有测试过具体损耗和耗时,只是直觉),如果有更优方案也可校验
证书校验
我们请求框架使用的OKHttp
,通过sslSocketFactory
方法设置证书认证对象X509TrustManager
HTTPSTrustManager httpsTrustManager = new HTTPSTrustManager();
builder.sslSocketFactory(httpsTrustManager.getKyeSSLSocketFactory(), httpsTrustManager);
builder.hostnameVerifier(new TrustAllHostnameVerifier());
当前我们做的是单校验,既客户端校验服务端
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// 未做实现
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// 客户端校验服务端
....
}
接下来就是如何校验SSL证书的问题了,这里有几个方案:
1、校验证书的颁发者、使用机构、颁发域名
2、校验证书的权威性,既是否正规CA机构颁发,也就是系统根目录中信任的证书
3、证书锁定
校验证书信息
使用机构、颁发域名
chain[0].getSubjectDN().getName() // 例如:CN=*.xxx.com,O=xxxx
颁发机构
chain[0].getIssuerDN().getName() // 例如:C=CN, O=TrustAsia Technologies, Inc., CN=TrustAsia RSA DV TLS CA G2
如果会更换证书供应商的话,不可校验颁发机构
校验证书的权威性
这里是利用设备中预先安装了权威CA机构颁发的根证书,受到了系统的信任
可以依赖OKHttp
官方的支持库
implementation("com.squareup.okhttp3:okhttp-tls:4.10.0")
okhttp-tls已经支持校验系统根证书,注意版本号应该跟okhttp版本号一致
HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder()
.addPlatformTrustedCertificates()
.build();
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager())
.build();
在一般情况中,这个方法就完全足够校验了
但在用户设备被root,系统信任的根证书目录被篡改的情况下,这个方法就行不通了。设备被root后,系统已经丧失了安全性,攻击者往系统信任的根证书目录下插入自签名的证书,这样一来,这个方案就不可行了
证书锁定
利用https握手时下发的公钥证书,与本地或者下发的证书公钥做比对,不一致则抛异常
RSAPublicKey pubkey = (RSAPublicKey)chain[0].getPublicKey();
String encoded = new BigInteger(1, pubkey.getEncoded()).toString(16);
final boolean expected =PUB_KEY_2023.equalsIgnoreCase(encoded) || PUB_KEY_2024.equalsIgnoreCase(encoded);
目前的方案是只做了静态配置公钥校验
后续需要迭代证书公钥动态下发能力
证书动态下发
1、由后端提供一个下发接口
2、这个接口保障其他接口的证书校验,在证书过期时也能访问,所以不能校验证书
3、不校验证书的情况下,有可能被劫持,下发攻击者自签名证书的公钥,从而让App信任攻击者的证书
4、所以接口必须加密,具体如下:
(1)非对称加密,后端生成秘钥和公钥,App持有公钥
(2)对称加密算法加密SSL证书的公钥字符串
(3)非对称加密算法的秘钥加密对称加密算法的秘钥
(4)接口出参两个字符串:加密后的SSL证书公钥、加密后的对称加密算法的秘钥
(5)客户端用非对称加密算法的公钥解密对称加密算法的秘钥,拿到对称加密算法的秘钥后解密SSL证书公钥
5、将证书公钥缓存下来
证书校验能力
后续考虑将证书校验拓展成原生能力,开放信任的证书池协助h5完成SSL证书校验工作