学徒浅析Android——Android7.0(N)对于自定义证书和非CA机构证书的适配校验
2017-10-19 21:49
2931 查看
本篇文章已授权微信公众号 guolin_blog(郭霖)独家发布
对于Android N之前自定义或非CA证书的使用,一般有两种方式:
1、自定义X509TurstManager和HostnameVerifier,替换原有的HttpsURLConnection中的校验类。自定义方法以烂大街的转载版为主。
2、在手机中安装证书。通过设置列表完成
其实从2016年5月17日起,Google
Play内容政策和开发者分发协议第 4.4条的相关规定中就指出将禁止发布X509TrustManager接口实施方式不安全的任何新应用或应用更新。传统的针对X509TurstManager和HostnameVerifier的修改不符合Google商店的发布要求。虽然我们在墙的庇护下,不用去google商店发布,但不可否认它确实是一种安全隐患,很容易遭受中间人攻击(MITM攻击)。
因此,在Android N中,google在这证书认证一块调整了原来的校验策略。即默认配置不会信任用户添加的CA证书。同时提供网络安全性配置规范证书的使用。
根据官网的介绍(https://developer.android.google.cn/training/articles/security-config.html#manifest ),我们可以通过设置network_security_config.xml对非CA证书进行适配。
鉴于官网的介绍缺少实例,在这里,我用12306网站的证书作为展示例子。
1、创建一个网络安全性配置network_security_config.xml,用来声明证书校验方式。
文件格式被设定为如下样子:
其中需要关注的是<certificates>,参数共有三种类型:
1、system 设备中预装的系统证书
2、user 用户自装证书
3、resourceID /raw文件下的证书
官网上指出,Android N的变化实际是对<base-config>的参数做了改动,面向Android 6.0(API级别23)及更低版本的应用的默认配置
面向 Android 7.0(API级别24)及更高版本应用的默认配置
也就是说,我们可以通过在应用中增加<certificates src="user" />,即可重新使用手机设备上的自装证书了。这一点该文作者已作出了证明(https://www.kalvin.cn/article/14);也可以通过域名<domain-config>指定证书的使用,但此时的证书只能配置在/raw中()
最终结合12306网站域名特点,network_security_config.xml编辑如下:
2、将应用对应的自定义证书放置到/res/raw/中。官网中尤其强调证书的格式,必须是DER或PEM格式编码,12306使用的srca.cer是DER格式编码,符合要求。我没有试过PEM格式的,手里只有P12格式的,转格式后没有成功,干扰因素太多,希望有人试验成功后交流一下。
3、在AndroidManifest.xml的<application>中声明network_security_config.xml
4、此时就可以正常访问www.12306.cn了。 综上就是Android N的适配方案,官网给出了方案固然是好事,但是不如例子看起来直观,还需要大家一起摸索啊,关于证书校验的问题,希望和大家一起成长。
附加:
当然,目前Android N的市场占比并不多,针对更多的旧版系统,使用自定义的X509TrustManager更为便捷。但是还是希望大家不要忽略校验,毕竟我们也算做产品的,最起码要对自己的东西负责啊。同时贴出我采用的方法(部分参考了X509TurestManagerImpl),方法如下:
对于Android N之前自定义或非CA证书的使用,一般有两种方式:
1、自定义X509TurstManager和HostnameVerifier,替换原有的HttpsURLConnection中的校验类。自定义方法以烂大街的转载版为主。
2、在手机中安装证书。通过设置列表完成
其实从2016年5月17日起,Google
Play内容政策和开发者分发协议第 4.4条的相关规定中就指出将禁止发布X509TrustManager接口实施方式不安全的任何新应用或应用更新。传统的针对X509TurstManager和HostnameVerifier的修改不符合Google商店的发布要求。虽然我们在墙的庇护下,不用去google商店发布,但不可否认它确实是一种安全隐患,很容易遭受中间人攻击(MITM攻击)。
因此,在Android N中,google在这证书认证一块调整了原来的校验策略。即默认配置不会信任用户添加的CA证书。同时提供网络安全性配置规范证书的使用。
根据官网的介绍(https://developer.android.google.cn/training/articles/security-config.html#manifest ),我们可以通过设置network_security_config.xml对非CA证书进行适配。
鉴于官网的介绍缺少实例,在这里,我用12306网站的证书作为展示例子。
1、创建一个网络安全性配置network_security_config.xml,用来声明证书校验方式。
文件格式被设定为如下样子:
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config>//应用范围的默认配置,只能配置一次。 <trust-anchors>//信任锚,就是信任数据源。 <certificates src="..."/>//证书。 ... </trust-anchors> </base-config> <domain-config>//域名级配置,针对待访问的网站域名,可以存在对多个,同时要考虑子域名,尽可能的将域名罗列详细。 <domain>android.com</domain> ... <trust-anchors> <certificates src="..."/> ... </trust-anchors> <pin-set>//证书公钥设置,用于证书固定,一般可以不用配置,除非确定证书长期有效。 <pin digest="...">...</pin> ... </pin-set> ...//如果域名之间存在父子关系,可以进行嵌套设置,同时对于证书的使用,可以用includeSubdomains对父域名设置,使证书具有继承属性。 </domain-config> ... <debug-overrides>//DEBUG调试时采用的信任源,只在DEBUG时起作用。 <trust-anchors> <certificates src="..."/> ... </trust-anchors> </debug-overrides> </network-security-config>
其中需要关注的是<certificates>,参数共有三种类型:
1、system 设备中预装的系统证书
2、user 用户自装证书
3、resourceID /raw文件下的证书
官网上指出,Android N的变化实际是对<base-config>的参数做了改动,面向Android 6.0(API级别23)及更低版本的应用的默认配置
<base-config cleartextTrafficPermitted="true"> <trust-anchors> <certificates src="system" /> <certificates src="user" /> </trust-anchors> </base-config>
面向 Android 7.0(API级别24)及更高版本应用的默认配置
<base-config cleartextTrafficPermitted="true"> <trust-anchors> <certificates src="system" /> </trust-anchors> </base-config>
也就是说,我们可以通过在应用中增加<certificates src="user" />,即可重新使用手机设备上的自装证书了。这一点该文作者已作出了证明(https://www.kalvin.cn/article/14);也可以通过域名<domain-config>指定证书的使用,但此时的证书只能配置在/raw中()
最终结合12306网站域名特点,network_security_config.xml编辑如下:
<network-security-config> <base-config cleartextTrafficPermitted="true"> <trust-anchors> <certificates src="system"/> </trust-anchors> </base-config> <domain-config> //网站域名,利用属性includeSubdomains,将配置的trust-anchors可以向下作用。 <domain includeSubdomains="true">www.12306.cn</domain> //自定义信任证书 <trust-anchors> //信任的自定义CA证书 <certificates src="@raw/srca" overridePins="true"/> </trust-anchors> //cleartextTrafficPermitted属性不详,这里是参考官网添加的 <domain-config cleartextTrafficPermitted="true"> //子域名 <domain includeSubdomains="true">kyfw.12306.cn</domain> </domain-config> </domain-config> </network-security-config>
2、将应用对应的自定义证书放置到/res/raw/中。官网中尤其强调证书的格式,必须是DER或PEM格式编码,12306使用的srca.cer是DER格式编码,符合要求。我没有试过PEM格式的,手里只有P12格式的,转格式后没有成功,干扰因素太多,希望有人试验成功后交流一下。
3、在AndroidManifest.xml的<application>中声明network_security_config.xml
android:networkSecurityConfig="@xml/network_securiy_config"
4、此时就可以正常访问www.12306.cn了。 综上就是Android N的适配方案,官网给出了方案固然是好事,但是不如例子看起来直观,还需要大家一起摸索啊,关于证书校验的问题,希望和大家一起成长。
附加:
当然,目前Android N的市场占比并不多,针对更多的旧版系统,使用自定义的X509TrustManager更为便捷。但是还是希望大家不要忽略校验,毕竟我们也算做产品的,最起码要对自己的东西负责啊。同时贴出我采用的方法(部分参考了X509TurestManagerImpl),方法如下:
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { //参数有效性校验 if (chain == null || chain.length == 0 || authType == null || authType.length() == 0) { throw new CertificateException(); } //证书有效性校验 for(X509Certificate cert : chain){ cert.checkValidity(); } //证书完整性校验 check(chain); } private static final int MIN_MODULUS = 1024; private static final String[] OID_BLACKLIST = {"1.2.840.113549.1.1.4"}; // MD5withRSA public static final void check(X509Certificate[] chain) throws CertificateException { for (X509Certificate cert : chain) { checkCert(cert); } } private static final void checkCert(X509Certificate cert) throws CertificateException { checkModulusLength(cert); checkNotMD5(cert); } private static final void checkModulusLength(X509Certificate cert) throws CertificateException { Object pubkey = cert.getPublicKey(); if (pubkey instanceof RSAPublicKey) { int modulusLength = ((RSAPublicKey) pubkey).getModulus().bitLength(); if (!(modulusLength >= MIN_MODULUS)) { throw new CertificateException("Modulus is < 1024 bits"); } } } private static final void checkNotMD5(X509Certificate cert) throws CertificateException { String oid = cert.getSigAlgOID(); for (String blacklisted : OID_BLACKLIST) { if (oid.equals(blacklisted)) { throw new CertificateException("Signature uses an insecure hash function"); } } } //HostnameVerifier可以借用源码HttpsUrlConnection中NoPreloadHolder中默认设置的OKHostnameVerifier进行域名合法性校验。 public boolean verify(String host, SSLSession session) { HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier(); return hv.verify(host, session); }
相关文章推荐
- Android自定义图片选取器,类似微信样式,自带裁剪功能,适配Android7.0
- android中对于枚举的自定义实现
- 【Android 应用开发】 自定义组件 宽高适配方法, 手势监听器操作组件, 回调接口维护策略, 绘制方法分析 -- 基于 WheelView 组件分析自定义组件
- Android 自定义Notification颜色适配问题
- Android 设备适配浅析 及 对dp的理解
- 学徒浅析Android开发——SO文件的混淆
- android自定义适配屏幕的ImageView
- 【Android - 自定义View】之自定义View浅析
- 在Android应用中使用自定义证书的HTTPS连接(下)
- android 对于多分辨率屏幕的适配
- Android 7.0系统校验证书流程
- 【Android 应用开发】 自定义组件 宽高适配方法, 手势监听器操作组件, 回调接口维护策略, 绘制方法分析 -- 基于 WheelView 组件分析自定义组件
- Android 属性自定义及使用获取浅析
- Android 自定义通知Notification 适配不同背景颜色
- Android 自定义Notification字体颜色适配
- Android7.0(Android N)适配教程,心得
- Android7.0(Android N)适配教程,心得
- 在Android应用中使用自定义证书的HTTPS连接(下)
- 浅析HTTPS中间人攻击与证书校验