博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
AFNetworking之AFSecurityPolicy深入学习
阅读量:6442 次
发布时间:2019-06-23

本文共 9297 字,大约阅读时间需要 30 分钟。

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

在开始之前先对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 *)sessiondidReceiveChallenge:(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证书链公钥

转载于:https://juejin.im/post/5a7128846fb9a01c9e4642cf

你可能感兴趣的文章
PHP实现留言板功能的思路
查看>>
apache默认虚拟主机
查看>>
0.osframe框架启动入门说明
查看>>
【gin-05】 GIN-使用jsoniter构建
查看>>
配置log4j日志热加载
查看>>
Linux文件、用户及组管理
查看>>
AI干货(一):为什么说基于机器学习的AI预测更智能?
查看>>
ios 应用之间的跳转和数据传输
查看>>
react 学习记录(三)
查看>>
hash值和hash算法
查看>>
curl 命令
查看>>
AngularUI团队封装的专用于AngularJS的前端UI库
查看>>
使用cookie管理会话
查看>>
用K-means聚类算法实现音调的分类与可视化
查看>>
cisco Vlan间通信之单臂路由
查看>>
Laravel配置PHP测试
查看>>
我的Emacs效果展示
查看>>
开源软件登录认证问题
查看>>
iptables如何开放被动模式的FTP服务
查看>>
CentOS-5.6-x86_64 下安装配置NFS
查看>>