Android 7.0关于HTTPS证书的适配讲解

Android 7.0关于HTTPS证书的适配讲解


今日科技快讯

近日,微信支付宣布自2017年12月1日起,将对每位用户每个自然月累计还款额超出5000元的部分按0.1%进行收费(最低0.1元),不超过5000元的部分仍然免费。不过微信支付表示,今年12月将进行手续费减免活动:每人每月累计还款20000元以下,手续费全部减免。同时未来也将不定期享有手续费随机减免优惠。

作者简介

本篇文章来自本公众号老司机作者 李政 的投稿。李政 同学也是有好长一段时间没有投稿了,那么本篇主要讲解了Android 7.0证书校验相关的知识内容,希望对大家有所帮助!

李政 的博客地址:

http://blog.csdn.net/lz8362

概述

对于Android N之前自定义或非CA证书的使用,一般有两种方式:

  • 自定义 X509TurstManager 和 HostnameVerifier,替换原有的 HttpsURLConnection 中的校验类。自定义方法以烂大街的转载版为主。

  • 在手机中安装证书。通过设置列表完成

  • 正文

    其实从2016年5月17日起,Google Play 内容政策和开发者分发协议第 4.4条的相关规定中就指出将禁止发布 X509TrustManager 接口实施方式不安全的任何新应用或应用更新。传统的针对 X509TurstManage r和 HostnameVerifier 的修改不符合 Google商店的发布要求。虽然我们在墙的庇护下,不用去 google 商店发布,但不可否认它确实是一种安全隐患,很容易遭受中间人攻击(MITM攻击)。

    因此,在Android N中,google在这证书认证一块调整了原来的校验策略。即默认配置不会信任用户添加的CA证书。同时提供网络安全性配置规范证书的使用。

    根据官网的介绍,地址如下:

    http://developer.android.google.cn/training/articles/security-config.html#manifest 

    我们可以通过设置 network_security_config.xml 对非CA证书进行适配。

    鉴于官网的介绍缺少实例,在这里,我用12306网站的证书作为展示例子。

  • 创建一个网络安全性配置 network_security_config.xml,用来声明证书校验方式。

  • 文件格式被设定为如下样子:

    <?xml version="1.0" encoding="utf-8"?>  

    <network-security-config>     <base-config>

    //应用范围的默认配置,只能配置一次。       <trust-anchors>//信任锚,就是信任数据源。           <certificates src="http://www.gunmi.cn/v/..."/>//证书。           ...       </trust-anchors>     </base-config>     <domain-config>

    //域名级配置,针对待访问的网站域名,可以存在对多个,同时要考虑子域名,尽可能的将域名罗列详细。       <domain>android.com</domain>       ...       <trust-anchors>           <certificates src="http://www.gunmi.cn/v/..."/>           ...       </trust-anchors>       <pin-set>

    //证书公钥设置,用于证书固定,一般可以不用配置,除非确定证书长期有效。           <pin digest="...">...</pin>           ...       </pin-set>  

           ..

    //如果域名之间存在父子关系,可以进行嵌套设置,同时对于证书的使用,可以用includeSubdomains对父域名设置,使证书具有继承属性。     </domain-config>     ...     <debug-overrides>

    //DEBUG调试时采用的信任源,只在DEBUG时起作用。       <trust-anchors>           <certificates src="http://www.gunmi.cn/v/..."/>           ...       </trust-anchors>     </debug-overrides>   </network-security-config>

    其中需要关注的是<certificates>,参数共有三种类型:

  • system      设备中预装的系统证书

  • user        用户自装证书

  • resourceID  /raw文件下的证书

  • 官网上指出,Android N的变化实际是对<base-config>的参数做了改动,面向Android 6.0(API级别23)及更低版本的应用的默认配置

    <base-config cleartextTrafficPermitted="true">      <trust-anchors>         <certificates src="http://www.gunmi.cn/v/system" />         <certificates src="http://www.gunmi.cn/v/user" />      </trust-anchors>  

    </base-config>

    面向 Android 7.0(API级别24)及更高版本应用的默认配置

    <base-config cleartextTrafficPermitted="true">      <trust-anchors>         <certificates src="http://www.gunmi.cn/v/system" />      </trust-anchors>  

    </base-config>

    也就是说,我们可以通过在应用中增加<certificates src="http://www.gunmi.cn/v/user" />,即可重新使用手机设备上的自装证书了。这一点该文作者已作出了证明,地址如下:

    http://www.kalvin.cn/article/14

    也可以通过域名<domain-config>指定证书的使用,但此时的证书只能配置在/raw中()。

    最终结合12306网站域名特点,network_security_config.xml 编辑如下:

    <network-security-config>      <base-config  cleartextTrafficPermitted="true">          <trust-anchors>              <certificates src="http://www.gunmi.cn/v/system"/>          </trust-anchors>      </base-config>      <domain-config>          //网站域名,利用属性includeSubdomains,将配置的trust-anchors可以向下作用。          <domain includeSubdomains="true">www.12306.cn</domain>          //自定义信任证书          <trust-anchors>              //信任的自定义CA证书              <certificates src="http://www.gunmi.cn/v/@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>

  • 将应用对应的自定义证书放置到 /res/raw/ 中。官网中尤其强调证书的格式,必须是 DER或 PEM 格式编码,12306使用的srca.cer是DER格式编码,符合要求。我没有试过PEM格式的,手里只有P12格式的,转格式后没有成功,干扰因素太多,希望有人试验成功后交流一下。

  • 在 AndroidManifest.xml 的 <application> 中声明 network_security_config.xml

  • android:networkSecurityConfig="@xml/network_securiy_config"
  • 此时就可以正常访问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 7.0关于HTTPS证书的适配讲解

    Android 7.0关于HTTPS证书的适配讲解