AFNetworking之AFSecurityPolicy深入学习

此篇文章主要是记录一下AFSecurityPolicy的学习过程, 在学习AFSecurityPolicy之前对HTTP做了一些了解, 并做了两篇笔记, 仅供参考.HTTP之网络基础HTTP通信.

在开始之前先对HTTPS的认证流程做一下梳理.

HTTPS认证
  1. 客户端发起网络请求, 会生成一个随机数N1和支持的版本号以及加密方式等传给服务器.
  2. 服务器接收到客户端的请求, 生成一个随机数N2, 将经过认证的数字证书和N2传给客户端.
  3. 客户端接收到服务器的证书, 验证证书的真伪, 如果证书没有问题, 就用服务器的公钥加密一个随机数N3, 这个时候客户端是知道N1, N2和N3的. 并将N3传给服务器.
  4. 服务器接收到加密后的N3, 使用自己的私钥将N3解密, 得到这个随机数. 使用N1+N2+N3作为对称密钥开始通信. 将加密后的数据传给客户端.
  5. 客户端接收到加密后的数据, 使用对称密钥解密. 获取到解密后的数据.

1. AFN中的使用

来看一下AFN中是如何使用AFSecurityPolicy进行HTTPS认证的.

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    ...
    if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
      ...
    }
    ...
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

在AFN中调用AFSecurityPolicyevaluateServerTrust:forDomain:方法验证服务器证书.

2. AFSecurityPolicy的核心方法

我们来看一下evaluateServerTrust:forDomain:方法.

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{
    /*
     allowInvalidCertificates:是否使用一个无效或者过期的证书(也就是使用自建证书)
     validatesDomainName:验证domain是否有效
     */
    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];
    // 是否要验签domain域
    if (self.validatesDomainName) {
        // 如果需要验证domain, 使用SecPolicyCreateSSL()创建验证策略
        // 第一个参数代表验证整个证书链, 第二个参数为域名
        [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
    } else {
        // 如果不验证domain, 就使用默认的验证策略, Returns a policy object for the default X.509 policy.
        [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
    }

    // 为serverTrust设置验证策略, 告诉客户端如何验证serverTrust
    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

    if (self.SSLPinningMode == AFSSLPinningModeNone) {
        // 如果SSLPinningMode==AFSSLPinningModeNone, 表示不适用SSL pinning, 但是允许自建证书或者使用AFServerTrustIsValid查看serverTrust是否可信.
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        // 即不允许自建证书, 并且serverTrust不可信, 就返回NO.
        return NO;
    }

    switch (self.SSLPinningMode) {
        case AFSSLPinningModeNone:
        default:
            return NO;
        // 这种模式表示用证书绑定方式(SSL Pinning)验证证书, 这里需要客户端保存有服务器的证书拷贝, 客户端保存的证书存在self.pinnedCertificates中
        case AFSSLPinningModeCertificate: {
            NSMutableArray *pinnedCertificates = [NSMutableArray array];
            for (NSData *certificateData in self.pinnedCertificates) {
                [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
            }
            // 设置锚点证书, 假如认证的证书是这个pinnedCertificates锚点证书的子节点, 那么就信任该证书.
            // 设置锚点证书是干嘛的??????
            SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

            if (!AFServerTrustIsValid(serverTrust)) {
                return NO;
            }

            // obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
            // 服务器端的证书链, 倒序
            NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
            
            // 遍历证书链, 查看是否和客户端的证书一直, 有的话说明服务端的证书可信.
            for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
                if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
                    return YES;
                }
            }
            
            return NO;
        }
        // 公钥验证, 这种模式值验证公钥, 不验证证书的有效期等, 只要公钥是正确的就可以
        case AFSSLPinningModePublicKey: {
            NSUInteger trustedPublicKeyCount = 0;
            // 从serverTrust取出服务器传过来的所有可用的证书, 并取出对应的公钥
            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;
}

在方法中, 加了一下自己理解的注释, 如果有不对的地方, 望指正.
方法的作用是检测服务器的证书是否可以信任.

allowInvalidCertificatesAFSecurityPolicy的一个属性, 是否允许信任一个无效证书(自建证书).
因为允许使用自建证书并且要验证域名, 那么SSLPinningMode的模式就不能为AFSSLPinningModeNone或者pinnedCertificates证书个数不能为0.

SSLPinningMode是一个枚举类型.

  • AFSSLPinningModeNone这种模式表示不适用SSL Pinning, 如果证书是CA认证机构颁发的证书, 就通过认证, 否则不通过.
  • AFSSLPinningModePublicKey这种模式是证书绑定模式验证证书, 客户端要保存证书的副本, 只验证证书的公钥.
  • AFSSLPinningModeCertificate这种模式是证书绑定模式验证证书, 客户端要保存证书的副本, 首先需要验证服务器证书的有效期, 身份信息等. 然后再将服务器证书和本地证书作比较, 查看是否一致.

pinnedCertificates客户端保存的服务器证书集合.

SecTrustSetPolicies是为serverTrust设置验证策略, 以哪种方式验证serverTrust.

使用AFSSLPinningModeNone模式验证证书, 如果是允许自建证书就认为证书是值得信任的. AFServerTrustIsValid是AFN自定义的函数, 验证serverTrust是否可信, 这个函数会在下边讲解. 如果serverTrust既不可信又不允许使用自建证书就表示此证书不可信.

使用AFSSLPinningModeCertificate模式验证证书, 先遍历pinnedCertificates获取客户端保存的证书, 并且使用SecTrustSetAnchorCertificates将证书集合设置为锚点证书, 这部分我也不太了解. 使用AFCertificateTrustChainForServerTrust方法获取服务器证书链, 获取到的证书是倒叙集合, 遍历这个集合, 如果本地证书集合包含服务器证书链中的证书, 说明这个证书是可信的.

使用AFSSLPinningModePublicKey模式验证证书, 使用AFPublicKeyTrustChainForServerTrust方法获取服务器所有可用证书对应的公钥并存入集合中, 两层for循环遍历服务器证书对应的公钥和客户端保存的公钥集合作比对, 如果证书公钥相同就将trustedPublicKeyCount信任公钥个数加1, 如果trustedPublicKeyCount个数大于0, 服务器公钥和客户端公钥有相同公钥, 表明证书可信.

3. AFSecurityPolicy中的辅助函数

AFSecurityPolicy中定义了一个函数供内部使用.

3.1 AFServerTrustIsValid函数

static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
    // 默认无效
    BOOL isValid = NO;
    // 枚举类型, 用来存储结果
    SecTrustResultType result;
    // 如果SecTrustEvaluate(serverTrust, &result)的结果为0, 就继续执行, 如果不为0就执行_out.
    // SecTrustEvaluate:评估serverTrust
    __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);

    // 如果是kSecTrustResultUnspecified 表示评估成功, 证书可以信任.
    // 如果是kSecTrustResultProceed 表示评估成功.
    isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);

_out:
    return isValid;
}

方法的作用是判断serverTrust是否有效, 代码中已经加了注释.
__Require_noErr_Quiet是一个宏定义, 使用到了goto语法, 当第一个参数不为0的时候就执行第二个参数指定的标签.
SecTrustEvaluate是系统的方法, 验证serverTrust, 并将验证结果赋值给第二个参数result.

3.2 AFCertificateTrustChainForServerTrust函数

static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust) {
    // 使用SecTrustGetCertificateCount
    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];
}

获取serverTrust相关的证书链

3.3 AFPublicKeyTrustChainForServerTrust函数

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

获取serverTrust证书链公钥

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

推荐阅读更多精彩内容