iOS三方框架之 - AFNetworking的Https认证流程

对AFNetworking进行解耦 AFNetworking解耦后能够分为如下几个模块: 1. NSURLSession:主要的一个基于NSURLSession的管理模块; 2. Reachability:网络监测模块; 3. Security:Https验证模块; 4. Serialization:序列化模块,包含了请求和响应的序列化; 5. UIKit:包含了一些UI的扩展,方便调用。api

1.jpg

NSURLSessionReachability 还有 SerializationUIKit 这些模块就不作过多的解析了,重点了解一下 Security中Https的认证。浏览器

Security

  • Https通信流程 详见之前的一篇文章:https通信流程,方便起见,主流程我截了张图:
    image.png

里面有关键的一步:即客户端验证服务端返回的公钥证书,这就是https的验证:安全

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
复制代码

只要是Https的请求,就会走到这个方法,该方法的做用就是处理服务器返回的证书, 须要在该方法中告诉系统是否须要安装服务器返回的证书,安装即表明公钥证书验证经过。bash

咱们来经过AFNetworking的源码来查看一下它是怎么处理的,源码里有详细注释:服务器

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    //NSURLAuthenticationChallenge 须要处理的挑战,里面包含了一个类型为 NSURLProtectionSpace 的属性
    //NSURLProtectionSpace里面包含了 host port  authenticationMethod等属性,还有服务器用于https验证的一些信息好比公钥、random_s等,这些都是服务器传过来的,authenticationMethod表示受权方式,对于AFNetworking来讲,是经过比较NSURLAuthenticationMethodServerTrust来决定是否须要证书验证


    /* 处理方式
     NSURLSessionAuthChallengeUseCredential:使用指定证书
    NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
    NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战
    NSURLSessionAuthChallengeRejectProtectionSpace:拒绝挑战,并尝试下一个验证保护空间,忽略证书参数
     */
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;


    /* NSURLCredential为一个证书,它决定了咱们如何处理这个挑战,它的初始化方法有:
     
     //通常用于须要处理身份验证401的错误验证
     - (instancetype)initWithUser:(NSString *)user password:(NSString *)password persistence:(NSURLCredentialPersistence)persistence
     
     //经过钥匙串中取得一个客户端证书来建立证书,通常用于服务端进行双向认证
     + (NSURLCredential *)credentialWithIdentity:(SecIdentityRef)identity certificates:(nullable NSArray *)certArray persistence:(NSURLCredentialPersistence)persistence
     
    //经过服务端传回的SecTrustRef(包含待验证的证书和支持的验证方法等)对象建立证书,通常客户端须要验证服务端身份时使用,即Https中的客户端对服务端证书验证这一步,收到服务端的challenge,例如https须要验证证书等 ats开启
     - (instancetype)initWithTrust:(SecTrustRef)trust
     */
    __block NSURLCredential *credential = nil;

    if (self.sessionDidReceiveAuthenticationChallenge) {
        //若是须要自定义处理当前的挑战,那么就经过block回调回去
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
        //挑战凭证不必定都是进行HTTPS证书的信任,也多是须要客户端提供用户密码或者提供双向验证时的客户端证书。当这个挑战凭证被验证经过时,请求即可以继续顺利进行
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            //开始验证证书 ,只须要验证服务端证书是否安全(即https的单向认证,这是AF默认处理的认证方,其余的认证方式,只能由咱们自定义Block的实现
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                //验证成功后,经过该证书建立一个NSURLCredential ,该证书会被传给服务端
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                //肯定挑战方式
                if (credential) {
                    //证书挑战
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    //默认挑战
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                 //验证失败,代表客户端没办法处理,那么就Cancel掉
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            //默认挑战
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        //将验证完成的证书发给服务器
        completionHandler(disposition, credential);
    }
}
复制代码

下面跳入真正作验证的方法: 先来认识几个属性:网络

  • SSLPinningMode :返回SSL Pinning的类型。默认的是AFSSLPinningModeNone。
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
    AFSSLPinningModeNone,        //表明无条件信任服务器的证书,这个模式表示不作SSL pinning,只跟浏览器同样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会经过,如果本身服务器生成的证书,这里是不会经过的。
    AFSSLPinningModePublicKey,   //表明会对服务器返回的证书中的PublicKey进行验证,客户端要有服务端的证书拷贝,验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通讯不会被窃听,由于中间人没有私钥,没法解开经过公钥加密的数据。
    AFSSLPinningModeCertificate, //表明会对服务器返回的证书同本地证书所有进行校验,须要客户端保存有服务端的证书拷贝,这里验证分两步,第一步验证证书的域名/有效期等信息,第二步是对比服务端返回的证书跟客户端返回的是否一致。
};
复制代码
  • pinnedCertificates:保存着全部的可用作校验的证书的集合。只要在证书集合中任何一个校验经过,evaluateServerTrust:forDomain: 就会返回true,即经过校验。
  • allowInvalidCertificates:使用容许无效或过时的证书,默认是NO不容许。
  • validatesDomainName:是否验证证书中的域名domain,默认是YES。

上一步生成证书的方法为:session

credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
//serverTrust为SecTrustRef类型的数据,就是待验证的对象。
复制代码
  • SecTrustRef:其实就是一个容器,里面装了服务器须要验证证书的基本信息、公钥等等,不只如此,它还能够装一些评估策略,还有客户端的锚点证书,这个客户端的证书,能够用来和服务端证书去匹配验证。 每个SecTrustRef对象包含多个SecCertificateRef 和 SecPolicyRef。其中 SecCertificateRef 可使用 DER 进行表示。
  • domain:服务器域名,用于域名验证
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{
    //若是有域名 设置了容许信任无效或过时证书  须要验证域名  没有提供证书或者不验证证书  返回NO
    if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
        return NO;
    }
    //用来装验证策略
    NSMutableArray *policies = [NSMutableArray array];

    //生成验证策略。若是要验证域名,就以域名为参数建立一个策略,不然建立默认的basicX509策略
    if (self.validatesDomainName) {
        // 若是须要验证domain,那么就使用SecPolicyCreateSSL函数建立验证策略,其中第一个参数为true表示为服务器证书验证建立一个策略,第二个参数传入domain,匹配主机名和证书上的主机名
        //1.__bridge:CF和OC对象转化时只涉及对象类型不涉及对象全部权的转化
        //2.__bridge_transfer:经常使用在讲CF对象转换成OC对象时,将CF对象的全部权交给OC对象,此时ARC就能自动管理该内存
        //3.__bridge_retained:(与__bridge_transfer相反)经常使用在将OC对象转换成CF对象时,将OC对象的全部权交给CF对象来管理
        [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
    } else {
        // 若是不须要验证domain,就使用默认的BasicX509验证策略
        [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
    }
    // 为serverTrust设置验证策略
    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

    if (self.SSLPinningMode == AFSSLPinningModeNone) {
        //不作SSL pinning,只在系统的信任机构列表里验证服务端返回的证书
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        //若是验证无效AFServerTrustIsValid,并且allowInvalidCertificates不容许自签,返回NO
        return NO;
    }

    switch (self.SSLPinningMode) {
        case AFSSLPinningModeNone:
        default:  //这里上面已经判断过了,执行到这里的话直接返回NO
            return NO;
        //客户端把服务端证书拷贝,存放在self.pinnedCertificates中
        case AFSSLPinningModeCertificate: {
            //所有校验(nsbundle .cer)
            NSMutableArray *pinnedCertificates = [NSMutableArray array];
            //把证书data,用系统api转成 SecCertificateRef 类型的数据,SecCertificateCreateWithData函数对原先的pinnedCertificates作一些处理,保证返回的证书都是DER编码的X.509证书
            for (NSData *certificateData in self.pinnedCertificates) {
                [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
            }

            // 将pinnedCertificates设置成须要参与验证的Anchor Certificate(锚点证书,经过SecTrustSetAnchorCertificates设置了参与校验锚点证书以后,假如验证的数字证书是这个锚点证书的子节点,即验证的数字证书是由锚点证书对应CA或子CA签发的,或是该证书自己,则信任该证书),具体就是调用SecTrustEvaluate来验证
            //serverTrust是服务器来的验证,有须要被验证的证书
            // 把本地证书设置为根证书,
            SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

            //再去验证证书
            if (!AFServerTrustIsValid(serverTrust)) {
                return NO;
            }

            //注意,这个方法和咱们以前的锚点证书不要紧了,是去从咱们须要被验证的服务端证书,去拿证书链。
            // 服务器端的证书链,注意此处返回的证书链顺序是从叶节点到根节点
            // 全部服务器返回的证书信息
            NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
            
            for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
                //若是咱们的证书中,有一个和它证书链中的证书匹配的,就返回YES
                // 是否本地包含相同的data
                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
                        trustedPublicKeyCount += 1;
                    }
                }
            }
            return trustedPublicKeyCount > 0;
        }
    }
    
    return NO;
}
复制代码
相关文章
相关标签/搜索