IOS应用安全-HTTP/HTTPS网络安全(二)

导读

本文主要讲解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)

  1. 配置ATS生效或不生效

    App Transport Security Settings字段下加入Allow Arbitrary Loads,或NSAllowsArbitraryLoads,配置为NO。PS:如果要禁用则为YES。但是如果配置为YES会导致审核失败,需要单独向APPStrore申诉说明。

  2. 配置web(H5)访问限制生效或不生效

    App Transport Security Settings字段下加入Allow Arbitrary Loads in Web ContentNSAllowsArbitraryLoadsInWebContent,默认配置生效为NO。如果要容许访问任意web网页内容,配置为YES。但是如果配置为YES会导致审核失败,需要单独向APPStrore申诉说明。

  3. 配置多媒体访问限制生效或不生效

    App Transport Security Settings字段下加入Allow Arbitrary Loads in Web Content,默认配置生效为NO。设置YES,容许访问通过AVFoundation框架访问媒体内容。

ATS根据域名配置(Exception Domains)

App Transport Security Settings字段下加入Exception DomainsNSExceptionDomains,系统优先响应NSExceptionDomains中的配置。比如之前设置NSAllowsArbitraryLoadsInMedia为 YES,然而NSExceptionDomain所代表的域名,如果没有特殊配置,依然默认不能访问不安全的媒体内容。

  1. 加入域名配置

    Exception Domains下,添加字典。其中key为域名的名称,比如baidu.com

  2. 容许访问HTTP

    在步骤1对应的域名字典下,加入字段NSExceptionAllowsInsecureHTTPLoads.默认为NO,如果设置YES,则容许访问HTTP

  3. 容许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 高级进阶

  4. 容许支持低版本的TLS算法。

    在步骤1对应的域名字典下,加入字段NSExceptionMinimumTLSVersion。值为对应的支持的最低版本。包含下面值

     TLSv1.0
     TLSv1.1
     TLSv1.2
  1. 包含域名下的所有子域名。

    在步骤1对应的域名字典下,加入字段NSIncludesSubdomains。默认为NO。如果配置为YES则包含域名下的所有子域名。

  2. 开启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。采用的策略为:

  1. 如果validatesDomainName == YES,则开启域名验证。如果allowInvalidCertificates == NO,则不容许所用的证书里面的域名和实际域名不一致。如果allowInvalidCertificates == YES,则忽略域名验证,直接按照AFSSLPinningMode方式验证。

  2. 如果validatesDomainName == NO,则不对证书做域名验证。

allowInvalidCertificates 说明

是否容许无效证书,默认为NO。采用的策略为:

  1. 如果allowInvalidCertificates == YES,则容许使用自制证书,或容许CA颁发的证书或系统信任的第三方证书(比如手动信任Charles证书)无效(包括域名无效和超过有效期)

  2. 如果allowInvalidCertificates == NO,那无法使用自制证书,且不容许CA颁发的证书或系统信任的第三方证书(比如手动信任Charles证书)超过有效期。如果配置了validatesDomainName == YES,则容许证书的域名不匹配,否则也不容许域名不匹配。

SSLPinningMode 说明

证书文件实体验证策略,默认为AFSSLPinningModeNone。AFSSLPinningMode包括的值为:

typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
 AFSSLPinningModeNone,
 AFSSLPinningModePublicKey,
 AFSSLPinningModeCertificate
};
  • AFSSLPinningModeNone。AFNetworking默认配置模式。采用的策略为:

    1. 如果容许无效证书(allowInvalidCertificates == YES),则直接返回验证成功(YES)

    2. 如果不容许无效证书(allowInvalidCertificates == YES),则验证证书是否有效:是否是CA颁发机构颁发的或者是否是系统信任的第三方证书(比如手动信任Charles证书)。如果另外配置了validatesDomainName == YES,则需要证书对应的域名是否匹配。如果配置了validatesDomainName == NO,则不验证证书对应的域名是否匹配。

  • AFSSLPinningModePublicKey。验证证书公钥模式。采用的策略为:

    1. 如果容许无效证书(allowInvalidCertificates == YES),则比对服务端发来的证书链中的公钥和自己加入的所有证书的的公钥是否匹配,只要有一个证书匹配就返回成功。

    2. 如果不容许无效证书(allowInvalidCertificates == YES),则先验证证书是否有效:是否是CA颁发机构颁发的或者是否是系统信任的第三方证书(比如手动信任Charles证书)。如果另外配置了validatesDomainName == YES,则需要证书对应的域名是否匹配。如果配置了validatesDomainName == NO,则不验证证书对应的域名是否匹配。验证通过后,则比对服务端发来的证书链中的公钥和自己加入的所有证书的的公钥是否匹配,只要有一个证书匹配就返回成功。

  • AFSSLPinningModeCertificate。证书完全匹配模式。采用的策略为:

    1. 如果容许无效证书(allowInvalidCertificates == YES),则将自己导入的所有证书作为锚点,判断服务端是否有效。如果有效,判断服务端证书链中的证书中,是否有证书包含在导入的证书里(使用二进制比较,也就是必须完全一样)。

    2. 如果不容许无效证书(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. 如何选择合适的方案?

    1. 建议对安全没有特别要求的或在测试环境下方便抓包,采用默认规则就可以了,重要的数据单独做加密。即选场景1
    1. 要校验域名,即:validatesDomainName不要设置为NO。如果设为NO,不校验域名,也最好自己加一层验证方法。
    1. 如果是自制证书,allowInvalidCertificates设置为YES。如果是ca颁发的证书则建议设置为YES。
    1. 无论是使用AFSSLPinningModePublicKey还是AFSSLPinningModeCertificate都应该考虑证书失效需要更换的问题。
    1. 如果用AFSSLPinningModePublicKey方式,使用场景6只要保证后续更换的证书公钥不变化就可以了。个人觉得是安全和方便性最平衡的一种模式,只要私钥不泄露就可以了。这个要求公司的证书管理机构知道这点,不过如果出了意外,也可以延缓部署。
    1. 最安全的方案是7。也就是强校验,漏洞最少,安全防护最高。但是必须考虑证书失效更换的问题。
  2. 如何防止证书过期导致不过的问题? 有以下方案:

    1. 可以用场景6,保证后续更换的证书公钥不变化就可以了
    1. APP强制升级,全局通知,热更新等保护通道,建议不要使用强校验策略,使用强的加密手段保证安全,作为最后手段。
    1. 加入证书更新的通道,每次应用启动的时候访问,查看是否有证书更新,如果有就去下载证书。
  3. 证书更新有什么方案?

    1. 建议启动检查是否有证书更新,可以合并在检查APP更新或热更新里面。
    1. 发现有更新的时候,服务端把证书二进制数据转为16进制字符串下发给客户端。服务端对数据使用私钥签名,客户端使用公钥对数据进行验签。
    1. 客户端将下载的文件按照签名等规则保存。下次加载前,继续对文件验证签名,保证没有篡改。
  4. 对于场景9,容许无效的证书,使用AFSSLPinningModeCertificate模式,为什么说明里面还说会验证证书过期?

    我个人也不确定,但是这个模式在加入证书锚点后,代码里还是会调用AFServerTrustIsValid()方法,然后再匹配证书数据是否一致。这个AFServerTrustIsValid()最终调用的是系统验证的方法,不确定系统是否还是会验证有效期,还是只验证包含证书就可以了,目前没有手段验证,大概率认为系统还是会验证是否过期。所以相对来说验证AFSSLPinningModePublicKey需要考虑更新的情况更少。

  5. 如果使用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格式。两种转化方式都可以:

  1. 命令行 openssl x509 -in 你的证书.crt -out 你的证书.cer -outform der

  2. 通过电脑导出。

    • 双击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执行系统默认的策略。因为项目中没用到,暂时不敢评判,下面是相关博客供参考。

参考文献

  1. iOS 10 适配 ATS(app支持https通过App Store审核)

  2. [ATS 官方文档] (https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW35)

  3. RSA的公钥和私钥到底哪个才是用来加密和哪个用来解密?

  4. iOS安全系列之二:HTTPS进阶

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容