导读
本文主要讲解IOS中ATS相关的配置说明和使用AFNetworking框架来实现证书验证的方法。讲解了AFNetworking各个配置试用的场景和注意点。
ATS
IOS9之后,苹果开启了App Transport Security(简称ATS)特性,即禁止HTTP请求,必须使用支持TLS1.2的HTTPS请求。但是也支持在Info.plist中做一些配置,来做缓冲。需要在info.plist中加入App Transport Security Settings
字段。
plist里面的结构如下
|
|
ATS整体配置(NSAllowsArbitraryLoads)
配置ATS生效或不生效
在
App Transport Security Settings
字段下加入Allow Arbitrary Loads
,或NSAllowsArbitraryLoads
,配置为NO。PS:如果要禁用则为YES。但是如果配置为YES会导致审核失败,需要单独向APPStrore申诉说明。配置web(H5)访问限制生效或不生效
在
App Transport Security Settings
字段下加入Allow Arbitrary Loads in Web Content
或NSAllowsArbitraryLoadsInWebContent
,默认配置生效为NO。如果要容许访问任意web网页内容,配置为YES。但是如果配置为YES会导致审核失败,需要单独向APPStrore申诉说明。配置多媒体访问限制生效或不生效
在
App Transport Security Settings
字段下加入Allow Arbitrary Loads in Web Content
,默认配置生效为NO。设置YES,容许访问通过AVFoundation框架访问媒体内容。
ATS根据域名配置(Exception Domains)
在App Transport Security Settings
字段下加入Exception Domains
或NSExceptionDomains
,系统优先响应NSExceptionDomains
中的配置。比如之前设置NSAllowsArbitraryLoadsInMedia为 YES,然而NSExceptionDomain所代表的域名,如果没有特殊配置,依然默认不能访问不安全的媒体内容。
加入域名配置
在
Exception Domains
下,添加字典。其中key为域名的名称,比如baidu.com
。容许访问HTTP
在步骤1对应的域名字典下,加入字段
NSExceptionAllowsInsecureHTTPLoads
.默认为NO,如果设置YES,则容许访问HTTP容许TLS支持非正向保密算法(Perfect Forward Secrecy)
在步骤1对应的域名字典下,加入字段
NSExceptionRequiresForwardSecrecy
.默认为YES。如果设置为NO,则支持非正向保密的加密算法。正向保密算法(Forward Secrecy),指如果通信密钥泄露,使用FS算法,可以保证这个密钥泄露只会影响之后的加密数据,之前的加密数据无法解密。主要防止攻击者保存之前的数据,等到私钥泄露之后再解密数据。这个算法的基础是基于椭圆曲线向前保密的秘钥交换算法ECDHE(Elliptic Curve Diffie-Hellman Ephemeral)。这些算法有:
1234567891011TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHATLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHATLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA如果设置为NO,则非正向保密算法,有下面几种:
123456TLS_RSA_WITH_AES_256_GCM_SHA384TLS_RSA_WITH_AES_128_GCM_SHA256TLS_RSA_WITH_AES_256_CBC_SHA256TLS_RSA_WITH_AES_256_CBC_SHATLS_RSA_WITH_AES_128_CBC_SHA256TLS_RSA_WITH_AES_128_CBC_SHA具体原理参考TLS/SSL 高级进阶。
容许支持低版本的TLS算法。
在步骤1对应的域名字典下,加入字段
NSExceptionMinimumTLSVersion
。值为对应的支持的最低版本。包含下面值123TLSv1.0TLSv1.1TLSv1.2包含域名下的所有子域名。
在步骤1对应的域名字典下,加入字段
NSIncludesSubdomains
。默认为NO。如果配置为YES则包含域名下的所有子域名。开启Certificate Transparency
在步骤1对应的域名字典下,加入字段
NSRequiresCertificateTransparency
,这个默认为NO.如果设为YES,则开启Certificate Transparency。这个是IETF启动的一个开源项目,目的是进一步验证证书是否安全。个人觉得没什么用,没必要开启。
ATS各种字段含义说明
主要的几个key:
NSAllowsArbitraryLoads
默认NO。如果设置为YES,则不生效ATS规则。但是配置在NSExceptionDomains里面的规则,按照里面的规则生效。配置为YES,提交APP Strore需要说明
NSAllowsArbitraryLoadsForMedia
默认NO.如果设置为YES,那使用AVFoundation加载资源不生效ATS。
NSAllowsArbitraryLoadsInWebContent
默认NO.如果设置为YES.使用webview加载的页面资源不生效ATS。
NSExceptionDomains
用于单独配置其他域名ATS策略的键。值应该是字典类型。
下面是NSExceptionDomains相关的key
NSIncludesSubdomains
默认NO。如果设置为YES,则生效此域名下的子域名
NSExceptionAllowsInsecureHTTPLoads
默认NO。如果设置为YES,则容许HTTP请求。设置YES,在审核时需要提供说明。
NSExceptionMinimumTLSVersion
默认TLSv1.2。可以设置为:TLSv1.0、TLSv1.1。在审核时需要提供说明
NSExceptionRequiresForwardSecrecy
默认YES。设置为NO标示不支持正向保密。
NSRequiresCertificateTransparency
默认NO。如果设置为YES,开启Certificate Transparency。
上面的是方便本人查找,详细设置case也可以参考[ATS 官方文档] (https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW35)
。目前过渡阶段最多出现的是第三方不兼容的问题+不支持NSExceptionRequiresForwardSecrecy+TLS版本不到1.2+h5访问的链接不支持ATS。按照要求配置就可以了,最重要的是推动第三方和自己后台使用HTTPS。自己的后台如果要求不高,可以用自制证书。
推荐的一个配置:
自己的域名使用最安全的方案,防止被苹果拒绝。第三方可以按照需求配置,但是审核时也建议进一步说明。
|
|
使用AFNetworking配置HTTPS安全
AFNetworking是最常用的网络框架。所以以这个为基础说明一些配置信息。本人是使用2.6版本的。3.x版本和2.6相比安全验证的逻辑没有变化,可以参考。2.6之前的版本,建议所有配置项显示配置,不要用默认配置(因为有个版本有漏洞,默认不校验域名)。下面先讲解下配置参数,清楚之后再讲解代码实现。
AFSecurityPolicy说明
AFNetworking使用AFSecurityPolicy
类来管理安全策略。
主要的属性和方法:
|
|
validatesDomainName 说明
是否容许证书包含的域名和实际访问的域名不匹配,默认为YES。采用的策略为:
- 如果validatesDomainName == YES,则开启域名验证。如果allowInvalidCertificates == NO,则不容许所用的证书里面的域名和实际域名不一致。如果allowInvalidCertificates == YES,则忽略域名验证,直接按照AFSSLPinningMode方式验证。
- 如果validatesDomainName == NO,则不对证书做域名验证。
allowInvalidCertificates 说明
是否容许无效证书,默认为NO。采用的策略为:
- 如果allowInvalidCertificates == YES,则容许使用自制证书,或容许CA颁发的证书或系统信任的第三方证书(比如手动信任Charles证书)无效(包括域名无效和超过有效期)
- 如果allowInvalidCertificates == NO,那无法使用自制证书,且不容许CA颁发的证书或系统信任的第三方证书(比如手动信任Charles证书)超过有效期。如果配置了validatesDomainName == YES,则容许证书的域名不匹配,否则也不容许域名不匹配。
SSLPinningMode 说明
证书文件实体验证策略,默认为AFSSLPinningModeNone。AFSSLPinningMode包括的值为:
|
|
- AFSSLPinningModeNone。AFNetworking默认配置模式。采用的策略为:
- 如果容许无效证书(allowInvalidCertificates == YES),则直接返回验证成功(YES)
- 如果不容许无效证书(allowInvalidCertificates == YES),则验证证书是否有效:是否是CA颁发机构颁发的或者是否是系统信任的第三方证书(比如手动信任Charles证书)。如果另外配置了validatesDomainName == YES,则需要证书对应的域名是否匹配。如果配置了validatesDomainName == NO,则不验证证书对应的域名是否匹配。
- AFSSLPinningModePublicKey。验证证书公钥模式。采用的策略为:
- 如果容许无效证书(allowInvalidCertificates == YES),则比对服务端发来的证书链中的公钥和自己加入的所有证书的的公钥是否匹配,只要有一个证书匹配就返回成功。
- 如果不容许无效证书(allowInvalidCertificates == YES),则先验证证书是否有效:是否是CA颁发机构颁发的或者是否是系统信任的第三方证书(比如手动信任Charles证书)。如果另外配置了validatesDomainName == YES,则需要证书对应的域名是否匹配。如果配置了validatesDomainName == NO,则不验证证书对应的域名是否匹配。验证通过后,则比对服务端发来的证书链中的公钥和自己加入的所有证书的的公钥是否匹配,只要有一个证书匹配就返回成功。
- AFSSLPinningModeCertificate。证书完全匹配模式。采用的策略为:
- 如果容许无效证书(allowInvalidCertificates == YES),则将自己导入的所有证书作为锚点,判断服务端是否有效。如果有效,判断服务端证书链中的证书中,是否有证书包含在导入的证书里(使用二进制比较,也就是必须完全一样)。
- 如果不容许无效证书(allowInvalidCertificates == YES),则先验证证书是否有效:是否是CA颁发机构颁发的或者是否是系统信任的第三方证书(比如手动信任Charles证书)。如果另外配置了validatesDomainName == YES,则需要证书对应的域名是否匹配。如果配置了validatesDomainName == NO,则不验证证书对应的域名是否匹配。验证通过后,则将自己导入的所有证书作为锚点,判断服务端证书是否有效。如果有效,判断服务端证书链中的证书中,是否有证书包含在导入的证书里(使用二进制比较,也就是必须完全一样)。
上面的比较绕,其实就是三个配置的组合。下面把这几种组合起来,看看验证了什么,使用于什么策略。其中AC表示allowInvalidCertificates,VD表示validatesDomainName。需要的可以去查这个表来决定方案。
场景 | mode | AC | VD | 验证策略 | 适用场景 | 不适用场景 |
---|---|---|---|---|---|---|
1 | None | NO | YES | 1.验证证书是否为信任的颁发机构颁发或是否为用户手动信任的证书 2.验证证书是否过期 3.验证证书域名是否匹配 |
1.AF默认的安全策略 2.对于安全有基础的要求 3.使用CA机构颁发的证书 |
1.使用自制证书的 2.不容许使用第三方抓包工具抓包的应用 |
2 | None | NO | NO | 1.验证证书是否为信任的颁发机构颁发或是否为用户手动信任的证书 2.验证证书是否过期 |
1.证书是正规CA颁发的。但是使用的域名不是证书中的域名 | 1.存在风险,会导致攻击方使用自己的合法的CA证书进行攻击 2.使用自制证书的 3.不容许使用第三方抓包工具抓包的应用 |
3 | None | YES | YES /NO | 不对证书做任何验证 | 请勿使用这儿配置。 1.对安全没有要求的 | 1.对安全有要求的 |
4 | PublicKey | NO | YES | 1.验证证书是否为信任的颁发机构颁发或是否为用户手动信任的证书 2.验证证书是否过期 3.验证证书域名是否匹配 4.验证证书和埋入的证书的公钥是否一致 |
1.证书是正规CA颁发的。 2.对安全有比较高的需求 3.需要本地APP中导入证书 4.禁止第三方工具抓包 5.证书过期后只要保证公钥一致,就可以保证请求有效 |
1.使用自制证书的 2.害怕攻击者拿到私钥或公钥文件,伪造证书(概率极低,因为需要CA机构再签发) 3.证书过期需要更换,但是新旧证书公钥不同 |
5 | PublicKey | NO | NO | 1.验证证书是否为信任的颁发机构颁发或是否为用户手动信任的证书 2.验证证书是否过期 3.验证证书和埋入的证书的公钥是否一致 |
1.证书是正规CA颁发的。 2.需要本地APP中导入证书 3.禁止第三方工具抓包 4.使用的域名和证书域名不一致 5.证书过期后只要保证公钥一致,就可以保证请求有效 |
1.使用自制证书的 2.害怕攻击者拿到私钥或公钥文件,伪造证书(概率极低,因为需要CA机构再签发) 3.证书过期需要更换,但是新旧证书公钥不同 |
6 | PublicKey | YES | YES/NO | 1.验证证书和埋入的证书公钥是否一致 |
1.使用自制证书 2.需要本地APP中导入证书 3.禁止第三方工具抓包 4.不需要关心证书的有效期 |
1.攻击者可以拿到私钥或公钥文件,伪造证书。相对于场景4和5,更容易攻击一些。 2.攻击者可以用不在有效期的证书对进行攻击 |
7 | Certificate | NO | YES | 1.验证证书域名是否匹配 2.验证证书是否为信任的颁发机构颁发或是否为用户手动信任的证书 3.验证证书是否过期 4.验证证书和埋入的证书是否完全一致 |
1.证书是正规CA颁发的。 2.对安全有最高的需求 3.需要本地APP中导入证书 4.禁止第三方工具抓包 |
1.需要考虑证书更新的场景 2.证书如果失效,客户端网络请求将会失效 3.自制证书 |
8 | Certificate | NO | NO | 1.验证证书是否为信任的颁发机构颁发或是否为用户手动信任的证书 2.验证证书是否过期 3.验证证书和埋入的证书是否完全一致 |
1.证书是正规CA颁发的。 2.对安全有最高的需求 3.需要本地APP中导入证书 4.禁止第三方工具抓包 5.证书域名和实际域名不一致 |
1.需要考虑证书更新的场景 2.证书如果失效,客户端网络请求将会失效 3.自制证书 4.攻击者拿到公私钥的前提下,可以利用不校验域名,攻击或重定向其他域名。 |
9 | Certificate | YES | YES | 1.验证证书的域名是否匹配? 2.验证证书是否过期? 3.验证证书和埋入的证书是否完全一致 |
1.使用自制证书 2.需要本地APP中导入证书 3.禁止中间人攻击 |
1.需要考虑证书更新的场景 2.证书如果失效,客户端网络请求将会失效 3.无法作废不安全的证书。在攻击者拿到公私钥的前提下,可以监听数据。 |
10 | Certificate | YES | NO | 1.验证证书是否过期? 2.验证证书和埋入的证书是否完全一致 |
1.使用自制证书 2.需要本地APP中导入证书 3.禁止禁止中间人攻击 |
1.需要考虑证书更新的场景 2.证书如果失效,客户端网络请求将会失效 3.攻击者拿到公私钥的前提下,可以利用不校验域名,定位到其他域名。 |
相关问题
下面是一些疑问:
如何选择合适的方案?
- 建议对安全没有特别要求的或在测试环境下方便抓包,采用默认规则就可以了,重要的数据单独做加密。即选场景1
- 要校验域名,即:validatesDomainName不要设置为NO。如果设为NO,不校验域名,也最好自己加一层验证方法。
- 如果是自制证书,allowInvalidCertificates设置为YES。如果是ca颁发的证书则建议设置为YES。
- 无论是使用AFSSLPinningModePublicKey还是AFSSLPinningModeCertificate都应该考虑证书失效需要更换的问题。
- 如果用AFSSLPinningModePublicKey方式,使用场景6只要保证后续更换的证书公钥不变化就可以了。个人觉得是安全和方便性最平衡的一种模式,只要私钥不泄露就可以了。这个要求公司的证书管理机构知道这点,不过如果出了意外,也可以延缓部署。
- 最安全的方案是7。也就是强校验,漏洞最少,安全防护最高。但是必须考虑证书失效更换的问题。
如何防止证书过期导致不过的问题?
有以下方案:- 可以用场景6,保证后续更换的证书公钥不变化就可以了
- APP强制升级,全局通知,热更新等保护通道,建议不要使用强校验策略,使用强的加密手段保证安全,作为最后手段。
- 加入证书更新的通道,每次应用启动的时候访问,查看是否有证书更新,如果有就去下载证书。
证书更新有什么方案?
- 建议启动检查是否有证书更新,可以合并在检查APP更新或热更新里面。
- 发现有更新的时候,服务端把证书二进制数据转为16进制字符串下发给客户端。服务端对数据使用私钥签名,客户端使用公钥对数据进行验签。
- 客户端将下载的文件按照签名等规则保存。下次加载前,继续对文件验证签名,保证没有篡改。
对于场景9,容许无效的证书,使用AFSSLPinningModeCertificate模式,为什么说明里面还说会验证证书过期?
我个人也不确定,但是这个模式在加入证书锚点后,代码里还是会调用
AFServerTrustIsValid()
方法,然后再匹配证书数据是否一致。这个AFServerTrustIsValid()
最终调用的是系统验证的方法,不确定系统是否还是会验证有效期,还是只验证包含证书就可以了,目前没有手段验证,大概率认为系统还是会验证是否过期。所以相对来说验证AFSSLPinningModePublicKey需要考虑更新的情况更少。如果使用AFSSLPinningModePublicKey模式,更换证书怎么保证公钥不变?
参考上一篇文章的附录,有一步是使用私钥
.key
文件生成.csr
。只要.key
和.csr
,下次签发的时候直接用这两文件,签发就可以了。这样能保证下次的证书公钥也不变化。建议生产私钥的时候使用位数在2048位以上,可以保证安全性。
代码具体实现
导出证书
建议向自己公司的网络管理员导出对应的crt文件。或者使用命令:
openssl s_client -connect www.google.com:443 </dev/null 2>/dev/null | openssl x509 -outform DER > https.cer //获取www.google.com:443的ssl证书,地址可以换成自己的
建议最好导出根证书的crt文件。因为根证书crt文件有效期长,很少更换。
如果是crt格式,使用时需要转化为cer格式。两种转化方式都可以:
命令行
openssl x509 -in 你的证书.crt -out 你的证书.cer -outform der
通过电脑导出。
- 双击crt,安装到钥匙链中。
- 钥匙链中选中需要导出的证书,鼠标右键,菜单中选择>>导出,点击存储即可。
然后将.cer文件导入到工程中。注意选Copy items if needed
.
设置生效规则
代码实现其实非常简单,重要的是规则的设置,建议认真搞清楚上面讲的配置说明,然后再配置。
|
|
具体配置请参考上面AFSecurityPolicy的介绍。通常测试环境下使用默认模式,其他环境使用校验模式。
验证策略源码解读
AF 2.6版本,在系统框架需要进行证书验证的时候会调用AFURLCOnnectionOpeation.m中的evaluateServerTrust:forDomain
方法:
|
|
里面实际验证是否有效的方法为:AFServerTrustIsValid(SecTrustRef serverTrust)
。实现:
|
|
WebView进行证书验证
如果不配置,webview执行系统默认的策略。因为项目中没用到,暂时不敢评判,下面是相关博客供参考。