导读
本文主要讲解IOS中ATS相关的配置说明和使用AFNetworking框架来实现证书验证的方法。讲解了AFNetworking各个配置试用的场景和注意点。
ATS
IOS9之后,苹果开启了App Transport Security(简称ATS)特性,即禁止HTTP请求,必须使用支持TLS1.2的HTTPS请求。但是也支持在Info.plist中做一些配置,来做缓冲。需要在info.plist中加入App Transport Security Settings
字段。
plist里面的结构如下
NSAppTransportSecurity : Dictionary {
NSAllowsArbitraryLoads : Boolean
NSAllowsArbitraryLoadsForMedia : Boolean
NSAllowsArbitraryLoadsInWebContent : Boolean
NSAllowsLocalNetworking : Boolean
NSExceptionDomains : Dictionary {
<domain-name-string> : Dictionary {
NSIncludesSubdomains : Boolean
NSExceptionAllowsInsecureHTTPLoads : Boolean
NSExceptionMinimumTLSVersion : String
NSExceptionRequiresForwardSecrecy : Boolean // Default value is YES
NSRequiresCertificateTransparency : Boolean
}
}
}
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)。这些算法有:
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
如果设置为NO,则非正向保密算法,有下面几种:
TLS_RSA_WITH_AES_256_GCM_SHA384 TLS_RSA_WITH_AES_128_GCM_SHA256 TLS_RSA_WITH_AES_256_CBC_SHA256 TLS_RSA_WITH_AES_256_CBC_SHA TLS_RSA_WITH_AES_128_CBC_SHA256 TLS_RSA_WITH_AES_128_CBC_SHA
具体原理参考TLS/SSL 高级进阶。
-
容许支持低版本的TLS算法。
在步骤1对应的域名字典下,加入字段
NSExceptionMinimumTLSVersion
。值为对应的支持的最低版本。包含下面值
TLSv1.0
TLSv1.1
TLSv1.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。自己的后台如果要求不高,可以用自制证书。
推荐的一个配置:
自己的域名使用最安全的方案,防止被苹果拒绝。第三方可以按照需求配置,但是审核时也建议进一步说明。
NSAppTransportSecurity
NSExceptionDomains
"domain-i-control.example.com" // 后台的域名
NSExceptionAllowsInsecureHTTPLoads = NO //不容许HTTP
NSExceptionRequiresForwardSecrecy = YES //支持正向加密
NSExceptionMinimumTLSVersion = "TLSv1.2" //使用1.2版本
NSIncludesSubdomains = YES //包含子域名
"other-domain-i-control.example.com" //部分不支持的第三方域名
NSExceptionAllowsInsecureHTTPLoads = NO //支持http
NSExceptionRequiresForwardSecrecy = YES //不支持正向加密
NSExceptionMinimumTLSVersion = "TLSv1.0" //第三方支持的TLS版本
NSAllowsArbitraryLoads = NO
使用AFNetworking配置HTTPS安全
AFNetworking是最常用的网络框架。所以以这个为基础说明一些配置信息。本人是使用2.6版本的。3.x版本和2.6相比安全验证的逻辑没有变化,可以参考。2.6之前的版本,建议所有配置项显示配置,不要用默认配置(因为有个版本有漏洞,默认不校验域名)。下面先讲解下配置参数,清楚之后再讲解代码实现。
AFSecurityPolicy说明
AFNetworking使用AFSecurityPolicy
类来管理安全策略。
主要的属性和方法:
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode; //证书验证的策略
@property (nonatomic, assign) BOOL allowInvalidCertificates; //是否容许无效的证书
@property (nonatomic, assign) BOOL validatesDomainName; //是否验证证书的域名
@property (nonatomic, strong, nullable) NSArray *pinnedCertificates; //app自己导入的证书文件,默认情况下主bundle里面的.cer文件都会导入到这个数组里。
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包括的值为:
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate
};
-
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 *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
//设置是否验证域名,不建议设置为NO
policy.validatesDomainName = YES;
//设置是否容许无效的证书,自制证书选YES
policy.allowInvalidCertificates = NO;
//AF如果模式为AFSSLPinningModeCertificate或AFSSLPinningModePublicKey会默认导入mainBundle里的所有cer文件,如果没有特别需求,没必要实现下面加载cer的代码
//先导入证书路径
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"myCer" ofType:@"cer"];//证书的路径
// 有多个加多个
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
policy.pinnedCertificates = @[certData];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
//生效policy
manager.securityPolicy = policy;
//调用
NSString *host = @"exmple";
NSDictionary *params = @{};
[manager GET:host
parameters:params
success:^(AFHTTPRequestOperation *_Nonnull operation, id _Nonnull responseObject) {
}
failure:^(AFHTTPRequestOperation *_Nonnull operation, NSError *_Nonnull error){
_hasUpdating = NO;
}];
具体配置请参考上面AFSecurityPolicy的介绍。通常测试环境下使用默认模式,其他环境使用校验模式。
验证策略源码解读
AF 2.6版本,在系统框架需要进行证书验证的时候会调用AFURLCOnnectionOpeation.m中的evaluateServerTrust:forDomain
方法:
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
//自制证书且验证域名不能用AFSSLPinningModeNone模式
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode ==
AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
// https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
// According to the docs, you should only trust your provided certs for evaluation.
// Pinned certificates are added to the trust. Without pinned certificates,
// there is nothing to evaluate against.
//
// From Apple Docs:
// "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
// Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
return NO;
}
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
//如果要验证域名需要把域名加入到规则里
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
//设置验证规则,如果有域名验证就加入域名验证
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) {
//AFSSLPinningModeNone下,如果容许无效证书或者证书通过验证就返回成功,否则返回失败
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
//其他模式,如果没有容许无效证书,就做证书验证,失效了就返回失败
return NO;
}
//抽取服务端的所有证书链数据
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
switch (self.SSLPinningMode) {
//不会进入到这个case
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
//将导入的证书加入到锚点里
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
// PS:查看相关文档发现,如果只调用了SecTrustSetAnchorCertificates而没有调用 SecTrustSetAnchorCertificatesOnly(serverTrust,false)方法,会导致只信任 SecTrustSetAnchorCertificatesOnly设置的锚点的证书,不信任系统默认内置的锚点证书
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
//验证是否证书是否在加入的锚点证书列表里。猜测会验证证书的有效期,如果有域名验证,验证域名。
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
//查看证书链中的证书是否和埋入的证书完全一致。
NSUInteger trustedCertificateCount = 0;
for (NSData *trustChainCertificate in serverCertificates) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
trustedCertificateCount++;
}
}
return trustedCertificateCount > 0;
}
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
//从证书链中抽取所有公钥列表
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
//验证证书链中的公钥是否在导入的证书里面的公钥链中
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
}
}
return NO;
}
里面实际验证是否有效的方法为:AFServerTrustIsValid(SecTrustRef serverTrust)
。实现:
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
// 具体实现,因为看不到源码无法确认,应该是标准的证书链验证方式,验证证书有效性,验证证书链的对应的CA根证书是否在颁发机构里或者是否是用户手动同意或拒绝的证书。如果设置了SecTrustSetAnchorCertificates,则验证是否在SecTrustSetAnchorCertificates方法设置的锚点证书里(不包含系统的证书)
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
//kSecTrustResultUnspecified:证书通过验证,但用户没有设置这些证书是否被信任
//kSecTrustResultProceed:证书通过验证,用户有操作设置了证书被信任,例如在弹出的是否信任的alert框中选择always trust
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out:
return isValid;
}
WebView进行证书验证
如果不配置,webview执行系统默认的策略。因为项目中没用到,暂时不敢评判,下面是相关博客供参考。