AFNetworking之AFSecurityPolicy深入学习

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

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

AFNetworking之AFSecurityPolicy深入学习_第1张图片
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证书链公钥

你可能感兴趣的:(AFNetworking之AFSecurityPolicy深入学习)