神刀安全网

iOS源码补完计划–AFNetworking(三)

iOS源码补完计划--AFNetworking(三)

目录

  • 前言
  • AFSecurityPolicy.h
    • 对服务器证书的验证策略
    • 属性
    • 获取证书
    • 自定义安全策略
    • 核心方法
  • AFSecurityPolicy.m
    • SecTrustRef
    • 从证书中提取公钥
    • 将公钥转化成NSData
    • 比对两个公钥是否相同
    • 返回服务器是否可以被信任
    • 取出所有服务器返回的证书
    • 取出服务器返回的所有证书中的公钥
    • AFSecurityPolicy对象方法
    • 核心方法- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
  • 参考资料

前言

AFNetworking源码第三篇
主要看了看AFSecurityPolicy的内容
负责网络安全策略(证书)的验证

作为一个辅助模块、代码量和文件都比较少
一行一行读下来就可以了
但是最好把HTTP/HTTPS好好理解一下、这里就先不提了。将来看网络协议的时候好好补一下。

AFN概述:《iOS源码补完计划–AFNetworking 3.1.0源码研读》

AFSecurityPolicy.h

对服务器证书的验证策略

typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {     AFSSLPinningModeNone,//无条件信任服务器的证书     AFSSLPinningModePublicKey,//对服务器返回的证书中的PublicKey进行验证     AFSSLPinningModeCertificate,//对服务器返回的证书同本地证书全部进行校验 }; 

属性

/**     SSLPinning 默认 `AFSSLPinningModeNone`  */ @property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;  /**     本地证书合集      默认、将会从整个工程目录下加载所有(.cer)的证书文件     如果想定制证书、可以使用`certificatesInBundle`来加载证书     然后调用`policyWithPinningMode:withPinnedCertificates`来创建一个新`AFSecurityPolicy`对象用于验证       如果证书合集中任何一个被校验通过、那么`evaluateServerTrust:forDomain:`都将返回true  */ @property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;  /**     使用允许无效或过期的证书 默认`NO`  */ @property (nonatomic, assign) BOOL allowInvalidCertificates;  /**     是否验证域名 默认`YES`  */ @property (nonatomic, assign) BOOL validatesDomainName; 

获取证书

/**     从指定`bundle`中获取证书合集     然后调用`policyWithPinningMode:withPinnedCertificates`来创建一个新`AFSecurityPolicy`对象用于验证  */ + (NSSet <NSData *> *)certificatesInBundle:(NSBundle *)bundle; 

自定义安全策略

/**     默认的安全策略     1、不允许无效或过期的证书     2、验证域名     3、不对证书和公钥进行验证  */ + (instancetype)defaultPolicy;  ///--------------------- /// @name Initialization ///---------------------  /**     通过指定的验证策略`AFSSLPinningMode`来创建  */ + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;  /**     通过指定的验证策略`AFSSLPinningMode`、以及证书合集来创建  */ + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates; 

核心方法

/**     根据具体配置、确定是否接受指定服务器的信任      服务器验证时会返回`NSURLCredential`challenge对象     @param serverTrust 使用challenge.protectionSpace.serverTrust参数即可     @param domain 使用challenge.protectionSpace.host即可   */ - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust                   forDomain:(nullable NSString *)domain; 

AFSecurityPolicy.m

其实整个.m文件也太多可以研究的地方、因为都是固定的方法。你只能这么写~
不过、一行一行看一看。iOS的证书到底是如何验证的、也不错。

  • SecTrustRef

整个验证都是基于SecTrustRef的、和.cer文件的关系大概是:
NSData格式的证书=>SecCertificateRef=>SecTrustRef对象
这个SecTrustRef通过

CFDataRef SecCertificateCopyData(SecCertificateRef certificate) SecKeyRef SecTrustCopyPublicKey(SecTrustRef trust) 

的方式又可以取出证书和公钥可见。
SecTrustRef就是一个内部至少携带了证书与公钥的结构体。

  • 从证书中提取公钥
static id AFPublicKeyForCertificate(NSData *certificate) {     id allowedPublicKey = nil;     SecCertificateRef allowedCertificate;     SecCertificateRef allowedCertificates[1];     CFArrayRef tempCertificates = nil;     SecPolicyRef policy = nil;     SecTrustRef allowedTrust = nil;     SecTrustResultType result;      //将二进制证书转化成`SecCertificateRef`     allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);     //如果allowedCertificate为空,则执行标记_out后边的代码     //__Require_Quiet&&_out和if&&else的意思差不多、好处是可以很多入口、然后统一出口     __Require_Quiet(allowedCertificate != NULL, _out);     //给allowedCertificates赋值     allowedCertificates[0] = allowedCertificate;     //新建CFArra: tempCertificates     tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);     //新建policy为X.509     policy = SecPolicyCreateBasicX509();     //创建SecTrustRef(`&allowedTrust`)对象。如果出错就跳到_out标记处      __Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);     //校验证书。这个不是异步的。如果出错也会调到_out标记处     __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);     //在SecTrustRef对象中取出公钥     allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);  _out:     //释放使用过的对象     if (allowedTrust) {         CFRelease(allowedTrust);     }      if (policy) {         CFRelease(policy);     }      if (tempCertificates) {         CFRelease(tempCertificates);     }      if (allowedCertificate) {         CFRelease(allowedCertificate);     }      return allowedPublicKey; } 

具体代码没啥可深究的、有需要的时候再去查查资料就行了
但有一点很有意思__Require_Quiet__Require_noErr_Quiet
作用其实和if-esle差不多、但是可以从多个入口跳到统一的出口、相关函数__Require_XXX基本都是这个意思。写了几个小方法、想看的自己可以copy运行一下

 #import <AssertMacros.h>       //断言为假则会执行一下第三个action、抛出异常、并且跳到_out     __Require_Action(1, _out, NSLog(@"直接跳"));     //断言为真则往下、否则跳到_out     __Require_Quiet(1,_out);     NSLog(@"111");          //如果不注释、从这里直接就会跳到out //    __Require_Quiet(0,_out); //    NSLog(@"222");          //如果没有错误、也就是NO、继续执行     __Require_noErr(NO, _out);     NSLog(@"333");          //如果有错误、也就是YES、跳到_out、并且抛出异常定位     __Require_noErr(YES, _out);     NSLog(@"444"); _out:     NSLog(@"end");  2018-05-17 14:18:12.656703+0800 AFNetWorkingDemo[4046:313255] 111 2018-05-17 14:18:12.656944+0800 AFNetWorkingDemo[4046:313255] 333 AssertMacros: YES == 0 ,  file: /Users/kiritoSong/Desktop/博客/KTAFNetWorkingDemo/AFNetWorkingDemo/AFNetWorkingDemo/ViewController.m, line: 39, value: 1 2018-05-17 14:18:12.657097+0800 AFNetWorkingDemo[4046:313255] end 
  • 将公钥转化成NSData
static NSData * AFSecKeyGetData(SecKeyRef key) {     CFDataRef data = NULL;      __Require_noErr_Quiet(SecItemExport(key, kSecFormatUnknown, kSecItemPemArmour, NULL, &data), _out);      return (__bridge_transfer NSData *)data;  _out:     if (data) {         CFRelease(data);     }      return nil; } 
  • 比对两个公钥是否相同
static BOOL AFSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) { #if TARGET_OS_IOS || TARGET_OS_WATCH || TARGET_OS_TV     return [(__bridge id)key1 isEqual:(__bridge id)key2]; #else     return [AFSecKeyGetData(key1) isEqual:AFSecKeyGetData(key2)]; #endif } 
  • 返回服务器是否可以被信任
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {     BOOL isValid = NO;     SecTrustResultType result;     //校验证书     __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);     //kSecTrustResultUnspecified:由非用户证书校验通过     //kSecTrustResultProceed:由用户证书校验通过     isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);  _out:     return isValid; } 
  • 取出所有服务器返回的证书
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]; } 

SecTrustRef:对象通过NSURLCredential传递进来的challenge.protectionSpace.serverTrust
也就是在外面通过- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(nullable NSString*)domain; 函数传递进来供我们校验的证书

  • 取出服务器返回的所有证书中的公钥
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]; } 
  • AFSecurityPolicy对象方法
@interface AFSecurityPolicy() @property (readwrite, nonatomic, assign) AFSSLPinningMode SSLPinningMode; @property (readwrite, nonatomic, strong) NSSet *pinnedPublicKeys; @end  @implementation AFSecurityPolicy  //取出某个bundle下所有的证书 + (NSSet *)certificatesInBundle:(NSBundle *)bundle {     NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];      NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]];     for (NSString *path in paths) {         NSData *certificateData = [NSData dataWithContentsOfFile:path];         [certificates addObject:certificateData];     }      return [NSSet setWithSet:certificates]; }  /**     取当前包内所有的证书  */ + (NSSet *)defaultPinnedCertificates {     static NSSet *_defaultPinnedCertificates = nil;     static dispatch_once_t onceToken;     dispatch_once(&onceToken, ^{         //根据当前类取出所在包位置         NSBundle *bundle = [NSBundle bundleForClass:[self class]];         _defaultPinnedCertificates = [self certificatesInBundle:bundle];     });      return _defaultPinnedCertificates; }   #pragma mark  <#               工厂方法             #> + (instancetype)defaultPolicy {     AFSecurityPolicy *securityPolicy = [[self alloc] init];     securityPolicy.SSLPinningMode = AFSSLPinningModeNone;      return securityPolicy; }  + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode {     return [self policyWithPinningMode:pinningMode withPinnedCertificates:[self defaultPinnedCertificates]]; }  + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates {     AFSecurityPolicy *securityPolicy = [[self alloc] init];     securityPolicy.SSLPinningMode = pinningMode;      [securityPolicy setPinnedCertificates:pinnedCertificates];      return securityPolicy; }  - (instancetype)init {     self = [super init];     if (!self) {         return nil;     }      self.validatesDomainName = YES;      return self; } //将证书的合集转化成公钥的合集并且赋值给self.pinnedPublicKeys持有备用 - (void)setPinnedCertificates:(NSSet *)pinnedCertificates {     _pinnedCertificates = pinnedCertificates;      if (self.pinnedCertificates) {         NSMutableSet *mutablePinnedPublicKeys = [NSMutableSet setWithCapacity:[self.pinnedCertificates count]];         for (NSData *certificate in self.pinnedCertificates) {             id publicKey = AFPublicKeyForCertificate(certificate);             if (!publicKey) {                 continue;             }             [mutablePinnedPublicKeys addObject:publicKey];         }         self.pinnedPublicKeys = [NSSet setWithSet:mutablePinnedPublicKeys];     } else {         self.pinnedPublicKeys = nil;     } } 
  • 核心方法
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust                   forDomain:(NSString *)domain {     //验证不通过     //host存在 && 允许使用过期证书(通常都是NO) && 验证域名 && (无条件信任服务器证书 || 没有证书)     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) {         //无条件信任服务器的证书         //允许使用过期或无效证书 || 服务器返回的证书可以信任 则返回YES否则NO         return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);     } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {         //服务器返回的证书不通过 && 不允许使用过期或无效证书 则不通过         return NO;     }           /*         代码走到这里、有两个条件         1、验证策略并不是无条件信任服务器的证书         2、服务器证书通过了信任并且不允许使用过期或无效的证书               也就是说证书没问题、但是需要进一步验证(公钥或者本地证书)      */               switch (self.SSLPinningMode) {         case AFSSLPinningModeNone:         default:             return NO;         case AFSSLPinningModeCertificate: {             //全部检查             NSMutableArray *pinnedCertificates = [NSMutableArray array];             for (NSData *certificateData in self.pinnedCertificates) {                 //将本地的二进制证书转化成SecCertificateRef证书并且加入数组                 [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;         }     }          return NO; } 

参考资料

AFNetworking 3.0 源码解读(二)之 AFSecurityPolicy
IOS 条件判断的几种形式

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » iOS源码补完计划–AFNetworking(三)

分享到:更多 ()