美文网首页IOS程序员iOS网络
iOS源码补完计划--AFNetworking(三)

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

作者: kirito_song | 来源:发表于2018-05-17 15:46 被阅读161次

    目录

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

    前言

    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;
    }
    

    API注释Demo

    把注释的源码放在了github上、有兴趣可以自取。

    GitHub


    参考资料

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

    相关文章

      网友评论

      本文标题:iOS源码补完计划--AFNetworking(三)

      本文链接:https://www.haomeiwen.com/subject/skxbdftx.html