AFSecurityPolicy是AFNetworking中负责对https请求进行证书验证的模块,本文主要是要搞清楚它是如何工做的。html
在介绍AFSecurityPolicy以前,咱们先来了解一下https以及一些相关概念。ios
简单来讲,https是运行在SSL/TLS之上的http,是为了提高数据传输的安全性的,使用到了对称加密和非对称加密算法。让咱们经过客户端与服务端的四次交互(四次握手)来详细看看https都作了些什么。算法
重点说下第2步和第3步。浏览器
第2步主要是要将服务端的公钥安全的发送给客户端,为此服务端主要作了两件事:安全
一、用服务端的私钥加密摘要服务器
二、传递CA颁发的用CA的私钥加密的包含服务端公钥的证书网络
而后到了客户端(也就是第3步),客户端分几步来验证:session
一、客户端先使用本地CA来验证证书是否授信,若是是权威机构颁发的证书,客户端会认为该证书是授信的(若是是本身搭建的CA,则会被认为是不授信的,会产生警告),同时也会验证证书的有效期,访问的域名是否一致等信息。app
二、客户端根据证书的要求生成证书编号dom
三、而后客户端会用本地CA公钥解密证书,拿到证书编号,若是生成的证书编号和拿到的证书编号一致,则认为证书没有问题,从而拿到服务端的公钥(简称为SK)
四、而后使用SK来解密服务端私钥加密的摘要(简称SS),而且本地用加密算法将内容加密成摘要(简称LS),对比SS == LS
须要说明一下CA的私钥加密的包含服务端公钥的证书,这个证书是用来保证信息不被中间人掉包的。由于证书编号是由CA的私钥加密的,即便是中间人也没法拿到CA的私钥,而客户端的本地CA公钥只能解密由CA的私钥加密的证书编号,因此中间人没法伪造证书。
那么假设中间人本身也申请一个CA的证书,而后客户端请求的时候原本要请求服务端的证书A,中间人拦截之后,发回本身的证书B给客户端,这个时候对于证书编号的验证就无论用了,可是,证书A和证书B的域名是不一样的,因此客户端在作验证的时候,就会认为证书不授信。
以上这些绕来绕去的流程就是https请求保证数据安全和防篡改的简易流程。也能够参考:
http://www.javashuo.com/article/p-snqwjvkn-bu.html
咱们在了解https的时候,会接触到一些相关常见的概念,如:SSL/TLS,openssl,PKI,CA,X.509等,下面咱们来简单了解一下。
SSL:(Secure Socket Layer,安全套接字层),用以保障在Internet上数据传输安全。
TLS:(Transport Layer Security,传输层安全协议),用于两个应用程序之间提供保密性和数据完整性。简单来看,能够认为TLS是SSL的升级版,比SSL更加安全。iOS9之后,已经要求TLS版本不低于1.2。
关于SSL和TLS的详情,能够参看:
http://www.cocoachina.com/ios/20150918/13488.html
http://seanlook.com/2015/01/07/tls-ssl/
PKI:(Public Key Infrastructure,公开密钥基础设施),是一个标准,用觉得全部网络应用提供加密和数字签名等密码服务及所必须的秘钥和证书管理体系。CA是PKI的核心。
CA:(Certificate Authority,证书认证中心),是一个负责发放和管理数字证书的第三方权威机构,它负责管理PKI结构下的全部用户(包括各类应用程序)的证书,把用户的公钥和用户的其余信息捆绑在一块儿,在网上验证用户的身份。CA机构的数字签名使得攻击者不能伪造和篡改证书。前文所说的服务端证书,就是由CA颁发的。
关于PKI和CA的详情,能够参看:
http://netsecurity.51cto.com/art/200602/21066.htm
X.509:是PKI体系中最重要的标准。是一些标准字段的集合,这些字段包含有关用户或设备及其相应公钥的信息。包含:版本号,公钥,算法,序列号,主题信息,有效期,认证机构,数字签名等。能够参考:https://baike.baidu.com/item/x509/1240109?fr=aladdin
openssl:一个强大的安全套接字层密码库,大概能够分红三个主要的功能部分。
一、libcryto
,这是一个具备通用功能的加密库,里面实现了众多的加密库。
二、libssl
,这个是实现ssl机制的,它是用于实现TLS/SSL的功能。
三、openssl,是个多功能命令行工具,它能够实现加密解密,甚至还能够当CA来用,可让你建立证书、吊销证书。
openssl生成私钥,公钥,并加密解密的简单实用以下:
openssl genrsa -out rsakey0.pem 1024 //生成1024位rsa私钥 openssl rsa -in rsakey0.pem -pubout -out rsaKeyPublic0 //生成公钥 openssl rsautl -encrypt -in 1.txt -inkey rsaKeyPublic0.pem -pubin -out 2.txt //公钥加密文件 openssl rsautl -decrypt -in 2.txt -inkey rsaKey0.pem -out 3.txt //私钥解密文件
在了解了https的过程及相关的概念做为铺垫之后,下面咱们来看看AFSecurityPolicy到底作了什么。
苹果已经为咱们封装好了https链接的创建,加密解密的过程,可是并无为咱们验证证书是否合法,这一步须要在AFSecurityPolicy里面完成。
当实用AFNetworking来发起https的请求时,会调用委托:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler{ }
这是一个质询,须要确认认证信息才能完成链接。
//判断服务器返回的证书类型,是否信任 if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; if (credential) { disposition = NSURLSessionAuthChallengeUseCredential;//使用指定的证书 } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling;//默认处理方式(忽略证书) } } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;//取消质询 } } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; }
重点看下:
//判断服务端来的证书是否验证经过 - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain { if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) { NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning."); return NO; } NSMutableArray *policies = [NSMutableArray array]; //是否验证域名,若是你的请求要直接用ip去连,能够忽略域名验证,可是有风险,咱们以前说过;下面不管if仍是else都是建立不一样的验证策略。 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) { //客户端是否信任无效或过时的证书(多是自签名证书)或者校验服务器传递的安全信息 serverTrust 是不是有效。 return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust); } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) { return NO; } switch (self.SSLPinningMode) { 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)]; } //将本地证书的数据设置为校验服务器安全信息 serverTrust 的锚证书 SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates); if (!AFServerTrustIsValid(serverTrust)) { return NO; } //获取全部服务端的证书,后面用以比较是否包含 NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust); for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) { if ([self.pinnedCertificates containsObject:trustChainCertificate]) { return YES; } } return NO; } 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; }
注释基本都加了,这里要说一下:SSLPinningMode(校验策略),分三种
AFSSLPinningModeNone: 这个模式表示不作SSL pinning,只跟浏览器同样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会经过,如果本身服务器生成的证书,这里是不会经过的。
AFSSLPinningModeCertificate:这个模式表示用证书绑定方式验证证书,须要客户端保存有服务端的证书拷贝,这里验证分两步,第一步验证证书的域名/有效期等信息,第二步是对比服务端返回的证书跟客户端返回的是否一致。须要考虑证书过时的问题,若是过时了,要想办法让app发起一个http请求,将续费的证书下载到沙盒中就能够了。
AFSSLPinningModePublicKey:这个模式一样是用证书绑定方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。不须要考虑证书过时
咱们来回顾一下AFSecurityPolicy的本地证书验证和咱们以前提的https请求本地验证有什么不一样。
https本地验证:是否权威机构的证书、域名是否一致、证书编号是否一致,都一致的话,就能够拿到服务端的公钥
AFSecurityPolicy验证:是否权威机构证书、域名是否一致(若是不验证域名,则忽略)、公钥验证或者证书验证
绿色的部分就是有差别的地方,其实证书编号是否一致苹果在底层已经作了。
若是选择AFSSLPinningModeNone,则二者是基本一致的,这也是默认策略。
可是若是选择其余两种,就表示在app内部放置了服务端的公钥证书(由于通常app请求的域名不会有太多,通常都是一个),这样的话就须要比较公钥证书或者公钥自己了,因此会多出来一步。可是这样作更加安全,对于防范中间人攻击更有效,回顾一下本文https部分应该比较容易理解。
AFSecurityPolicy的参考文章:
https://blog.csdn.net/u011374318/article/details/79364995
http://www.javashuo.com/article/p-utnaydtp-er.html
最后,当咱们经过了解https的请求过程,了解相关知识,了解如何防范中间人攻击,绕了这么大一圈后,再来理解AFSecurityPolicy,会发觉容易不少。