AFNetworking3源码阅读-Security 自 iOS9 发布之后,由于新特性 App Transport Security 的引入,在默认行为下是不能发送 HTTP 请求的。很多网站都在转用 HTTPS,而 AFNetworking 中的 AFSecurityPolicy 就是为了阻止中间人攻击,以及其它漏洞的工具。 AFSecurityPolicy 主要作用就是验证 HTTPS 请求的证书是否有效,如果 app 中有一些敏感信息或者涉及交易信息,一定要使用 HTTPS 来保证交易或者用户信息的安全。
 
https & SSL Pinning https HTTPS其实是有两部分组成:HTTP + SSL / TLS,也就是在HTTP上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过TLS进行加密
 
SSL Pinning 可 以理解为证书绑定,是指客户端直接保存服务端的证书,建立https连接时直接对比服务端返回的和客户端保存的两个证书是否一样,一样就表明证书 是真的,不再去系统的信任证书机构里寻找验证。这适用于非浏览器应用,因为浏览器跟很多未知服务端打交道,无法把每个服务端的证书都保存到本地,但CS架构的像手机APP事先已经知道要进行通信的服务端,可以直接在客户端保存这个服务端的证书用于校验。
为什么直接对比就能保证证书没问题? 如果中间人从客户端取出证书,再伪装成服务端跟其他客户端通信,它发送给客户端的这个证书不就能通过验证吗?确实可以通过验证,但后续的流程走不下去,因为下一步客户端会用证书里的公钥加密,中间人没有这个证书的私钥就解不出内容,也就截获不到数据,这个证书的私钥只有真正的服务端有,中间人伪造证书主要伪造的是公钥。
为什么要用SSL  Pinning?正常的验证方式不够吗?如果服务端的证书是从受信任的的CA机构颁发的,验证是没问题的,但CA机构颁发证书比较昂贵,小企业或个人用 户 可能会选择自己颁发证书,这样就无法通过系统受信任的CA机构列表验证这个证书的真伪了,所以需要SSL Pinning这样的方式去验证。
 
AFSecurityPolicy 
NSURLConnection 已经封装了https连接的建立、数据的加密解密功能,我们直接使用NSURLConnection是可以访问 https网站的,但NSURLConnection并没有验证证书是否合法,无法避免中间人攻击。 要做到真正安全通讯,需要我们手动去验证服务端返回的 证书,AFSecurityPolicy封装了证书验证的过程,让用户可以轻易使用,除了去系统信任CA机构列表验证,还支持SSL  Pinning方式的验证
 
使用 
把服务端证书(需要转换成cer格式)放到APP项目资源里,AFSecurityPolicy会自动寻找根目录下所有cer文件
 
1 2 3 AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey]; securityPolicy.allowInvalidCertificates = YES ; [AFHTTPRequestOperationManager manager].securityPolicy = securityPolicy; 
 
验证时 使用:
1 2 - (BOOL )evaluateServerTrust:(SecTrustRef)serverTrust                   forDomain:(NSString  *)domain; 
 
结构 UML图如下:
 
AFSSLPinningMode 验证模式 
AFSSLPinningModeNone
  这个模式表示不做SSL pinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会通过,若是自己服务器生成的证书,这里是不会通过的。
 
AFSSLPinningModeCertificate
  这个模式表示用证书绑定方式验证证书,需要客户端保存有服务端的证书拷贝,这里验证分两步,第一步验证证书的域名/有效期等信息,第二步是对比服务端返回的证书跟客户端返回的是否一致。
 
AFSSLPinningModePublicKey
  这个模式同样是用证书绑定方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据。
 
 
验证服务端是否受信 
验证服务端是否守信是通过 - [AFSecurityPolicy evaluateServerTrust:forDomain:] 方法进行的。
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 - (BOOL )evaluateServerTrust:(SecTrustRef)serverTrust                   forDomain:(NSString  *)domain{ 	1 : 不能隐式地信任自己签发的证书 	2 : 设置 policy 	3 : 验证证书是否有效 	4 : 根据 SSLPinningMode 对服务端进行验证 	 	return  NO ;                    } 
 
1#  不能隐式地信任自己签发的证书
1 2 3 4 5 6 7 8 9       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 ; } 
 
2#  设置 policy
1 2 3 4 5 6 7 8 9 10 11 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); 
 
3#  验证证书是否有效
1 2 3 4 5 6 7 8 if  (self .SSLPinningMode == AFSSLPinningModeNone) {    return  self .allowInvalidCertificates || AFServerTrustIsValid(serverTrust); } else  if  (!AFServerTrustIsValid(serverTrust) && !self .allowInvalidCertificates) {    return  NO ; } 
 
4#  根据 SSLPinningMode 对服务端进行验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 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)];             }                          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 ;         }     } 
 
在上面用到了一些静态方法,如下:
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 static  id  AFPublicKeyForCertificate(NSData  *certificate) {    id  allowedPublicKey = nil ;     SecCertificateRef allowedCertificate;     SecPolicyRef policy = nil ;     SecTrustRef allowedTrust = nil ;     SecTrustResultType result;          allowedCertificate = SecCertificateCreateWithData(NULL , (__bridge CFDataRef )certificate);     __Require_Quiet(allowedCertificate != NULL , _out );          policy = SecPolicyCreateBasicX509();          __Require_noErr_Quiet(SecTrustCreateWithCertificates(allowedCertificate, policy, &allowedTrust), _out );          __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out );          allowedPublicKey = (__bridge_transfer id )SecTrustCopyPublicKey(allowedTrust); _out :     if  (allowedTrust) {         CFRelease (allowedTrust);     }     if  (policy) {         CFRelease (policy);     }     if  (allowedCertificate) {         CFRelease (allowedCertificate);     }     return  allowedPublicKey; } 
 
1 2 3 4 5 6 7 8 9 10 11 12 static  BOOL  AFServerTrustIsValid(SecTrustRef serverTrust) {    BOOL  isValid = NO ;     SecTrustResultType result;     __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out );               isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed); _out :     return  isValid; } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static  NSArray  * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust) {              CFIndex  certificateCount = SecTrustGetCertificateCount(serverTrust);     NSMutableArray  *trustChain = [NSMutableArray  arrayWithCapacity:(NSUInteger )certificateCount];          for  (CFIndex  i = 0 ; i < certificateCount; i++) {         SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);         [trustChain addObject:(__bridge_transfer NSData  *)SecCertificateCopyData(certificate)];     }     return  [NSArray  arrayWithArray:trustChain]; } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 static  NSArray  * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {         SecPolicyRef policy = SecPolicyCreateBasicX509();     CFIndex  certificateCount = SecTrustGetCertificateCount(serverTrust);     NSMutableArray  *trustChain = [NSMutableArray  arrayWithCapacity:(NSUInteger )certificateCount];     for  (CFIndex  i = 0 ; i < certificateCount; i++) {         SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);         SecCertificateRef someCertificates[] = {certificate};         CFArrayRef  certificates = CFArrayCreate (NULL , (const  void  **)someCertificates, 1 , NULL );         SecTrustRef trust;                  __Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out );         SecTrustResultType result;                  __Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out );                  [trustChain addObject:(__bridge_transfer id )SecTrustCopyPublicKey(trust)];     _out :                  if  (trust) {             CFRelease (trust);         }         if  (certificates) {             CFRelease (certificates);         }         continue ;     }     CFRelease (policy);          return  [NSArray  arrayWithArray:trustChain]; } 
 
与 AFURLSessionManager 协作 在代理协议 - URLSession:didReceiveChallenge:completionHandler: 或者 - URLSession:task:didReceiveChallenge:completionHandler: 代理方法被调用时会运行这段代码
1 2 3 4 5 6 7 8 9 10 if  ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust ]) {      if  ([self .securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {         disposition = NSURLSessionAuthChallengeUseCredential ;         credential = [NSURLCredential  credentialForTrust:challenge.protectionSpace.serverTrust];     } else  {         disposition = NSURLSessionAuthChallengeRejectProtectionSpace ;     } } else  {     disposition = NSURLSessionAuthChallengePerformDefaultHandling ; } 
 
NSURLAuthenticationChallenge 表示一个认证的挑战,提供了关于这次认证的全部信息。它有一个非常重要的属性 protectionSpace,这里保存了需要认证的保护空间, 每一个 NSURLProtectionSpace 对象都保存了主机地址,端口和认证方法等重要信息。
在上面的方法中,如果保护空间中的认证方法为 NSURLAuthenticationMethodServerTrust,那么就会使用在上一小节中提到的方法 - [AFSecurityPolicy evaluateServerTrust:forDomain:] 对保护空间中的 serverTrust 以及域名 host 进行认证
根据认证的结果,会在 completionHandler 中传入不同的 disposition 和 credential 参数。