美文网首页
AFNetworking源码分析之安全策略

AFNetworking源码分析之安全策略

作者: hallfrita | 来源:发表于2018-10-16 16:00 被阅读0次

    在网络请求中,我们经常使用http协议,http全称也就是超文本传输协议,通常使用版本是1.1版本,为了安全我们会使用https。这两个的区别就在于https是http经过加密的http,所以https更安全。正因为https有了加密这个过程,也使得https的响应速度没有http快。

    http

    http的历程
    距今,http共有四个版本,分别是1991年的0.9版本的推出,这个版本仅支持GET命令;1996年推出了1.0版本,这个版本支持GET、POST、HEAD,增加了请求头,但是这个版本每次TCP连接只支持一次请求;半年后1997年就推出了我们现在使用最广泛的1.1版本,支持持久连接,还提出了管道机制;虽然1.1版本支持客户端同时发送多个请求,但是服务器的响应还是依次执行,所以后来推了2.0版本,这个版本就解决了服务器可以同时响应多个请求,还压缩了请求头,服务器还能自动推送静态资源

    持久连接:就是指client和server之间保持持久连接,只有在双方长时间无响应的状态下才会断开连接。减少TCP连接的重复建立和断开所造成的额外开销
    管道机制:同一个TCP连接可以发多个请求

    http其实是无状态的,也就是说client和server之间不做状态保存,就减少了CPU和内存的消耗。但是这样就不能维持一个双向的会话,所以就用cookie来保存这个状态,比如我们开发中请求头中会有一个seeeionId什么的。

    http的缺点:
    1、明文传输,内容容易被窃听
    2、没有验证通信方的身份,容易遭遇伪装
    3、无法验证报文的完整性,可能遭遇篡改

    因为如此,所以我们要对http进行加密。加密又分为通信加密和内容加密。内容加密容易被篡改,而通信加密通过ssl建立安全套接层,建立一个安全的套接线路,所以就有了https。

    https

    https都不能说是一种协议,他只是http之上做了一层ssl加密
    ssl加密不仅提供了加密处理,还提供了证书,确保了通信线路的安全
    https = http + 加密 + 认证 + 完整性保护

    证书有CA证书和自签证书。CA证书是第三方机构给我们颁发的可信的证书

    加密
    1、共享密钥加密:加密和解密公用同一个密钥,又叫对称加密,比较快
    2、公开私钥加密:有公开密钥和私有密钥,较慢

    https上面两种加密都用了,在交换密钥使用公开密钥加密,在通信交换报文使用共享秘钥加密

    https的缺点:通过ssl加密,处理速度会变慢,消耗CPU和内存资源,通信时间会延长。

    AFSecurityPolicy

    AFSecurityPolicy的作用就是客户端的证书验证,对于CA证书的验证我们不需要做什么,除非是自签证书,需要把证书放到本地。

    如果是自签证书我们要做些什么呢?如下实例代码

    - (void)test{
        NSString *urlstr = @"http://www.12306.cn/mormhweb/";
        AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
        // 默认这个属性不用管,默认就是CA证书的认证,除非是银行等相关对安全要求比较高的应用,才会走自签证书的认证,才会去调用下面的方法
        manager.securityPolicy = [self securityPolicy];
        [manager GET:urlstr parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
            
        } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            
        }];
    }
    
    - (AFSecurityPolicy *)securityPolicy{
        NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"srca" ofType:@"cer"];
        NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
        NSSet *set = [NSSet setWithObject:cerData];
        AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:set];
        securityPolicy.allowInvalidCertificates = YES;
        securityPolicy.validatesDomainName = NO;
        return securityPolicy;
    }
    

    不管是CA证书还是自签证书的认证,AFN都已经为我们封装好,具体怎么实现,我们一步一步看源码

    AFSSLPinningMode都有哪值呢?

    typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
         //不使用固定证书(本地)验证服务器。直接从客户端系统中的受信任颁发机构 CA 列表中去验证,默认
        AFSSLPinningModeNone,
        // 代表会对服务器返回的证书中的PublicKey进行验证,通过则通过,否则不通过
        AFSSLPinningModePublicKey,
        // 代表会对服务器返回的证书同本地证书全部进行校验,通过则通过,否则不通过
        AFSSLPinningModeCertificate,
    };
    

    什么时候需要证书认证呢?就是在NSURLSessionDataDelegate的回调方法中

    //收到服务端的challenge,例如https需要验证证书等 ats开启
    - (void)URLSession:(NSURLSession *)session
    didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
     completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
    {
    
    
        //挑战处理类型为 默认
        /*
         NSURLSessionAuthChallengeUseCredential:使用指定的证书
         NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
         NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战
         NSURLSessionAuthChallengeRejectProtectionSpace:拒绝此挑战,并尝试下一个验证保护空间;忽略证书参数
         */
    
        //挑战处理类型为默认
        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        __block NSURLCredential *credential = nil;//证书
    
        // 自定义方法,用来如何应对服务器端的认证挑战
        if (self.sessionDidReceiveAuthenticationChallenge) {
            disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
        } else {
    
            // 1.判断接收服务器挑战的方法是否是信任证书
            if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
                //只需要验证服务端证书是否安全(即https的单向认证,这是AF默认处理的认证方式,其他的认证方式,只能由我们自定义Block的实现
                if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                     // 2.信任评估通过,就从受保护空间里面拿出证书,回调给服务器,告诉服务,我信任你,你给我发送数据吧.
                    credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                   // 确定挑战的方式
                    if (credential) {
                        //证书挑战
                        disposition = NSURLSessionAuthChallengeUseCredential;
                    } else {
                        //默认挑战
                        disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                    }
                } else {
                     //取消挑战
                    disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
                }
            } else {
                //默认挑战方式
                disposition = NSURLSessionAuthChallengePerformDefaultHandling;
            }
        }
     //完成挑战
        // 3.将信任凭证发送给服务端
        if (completionHandler) {
            completionHandler(disposition, credential);
        }
    }
    

    其中校验最关键的方法就是AFSecurityPolicy提供的这个方法

    -(BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(nullable NSString *)domain;

    这个方法做了什么呢?我们看源码

    - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                      forDomain:(NSString *)domain
    {
    
           //如果有服务器域名、设置了允许信任无效或者过期证书(自签名证书)、需要验证域名、没有提供证书或者不验证证书,返回no。后两者和allowInvalidCertificates为真的设置矛盾,说明这次验证是不安全的。
        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."
            //如果想要实现自签名的HTTPS访问成功,必须设置pinnedCertificates,且不能使用defaultPolicy
            NSLog(@"In order to val idate a domain name for self signed certificates, you MUST use pinning.");
            //不受信任,返回
            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设置验证策略,用策略对serverTrust进行评估
        SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
    
    //如果是AFSSLPinningModeNone(不做本地证书验证,从客户端系统中的受信任颁发机构 CA 列表中去验证服务端返回的证书)
        if (self.SSLPinningMode == AFSSLPinningModeNone) {
            //不使用ssl pinning 但允许自建证书,直接返回YES;否则进行第二个条件判断,去客户端系统根证书里找是否有匹配的证书,验证serverTrust是否可信,直接返回YES
            
            //why需要allowInvalidCertificates?
            return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
        } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
            //如果验证无效AFServerTrustIsValid,而且allowInvalidCertificates不允许自签,返回NO
            return NO;
        }
    
        switch (self.SSLPinningMode) {
    //上一部分已经判断过了,如果执行到这里的话就返回NO
            case AFSSLPinningModeNone:
            default:
                return NO;
                //验证证书类型
                // 这个模式表示用证书绑定(SSL Pinning)方式验证证书,需要客户端保存有服务端的证书拷贝
                // 注意客户端保存的证书存放在self.pinnedCertificates中
            case AFSSLPinningModeCertificate: {
                // 全部校验(nsbundle .cer)
                NSMutableArray *pinnedCertificates = [NSMutableArray array];
                //把证书data,用系统api转成 SecCertificateRef 类型的数据,SecCertificateCreateWithData函数对原先的pinnedCertificates做一些处理,保证返回的证书都是DER编码的X.509证书
                for (NSData *certificateData in self.pinnedCertificates) {
                    //cf arc brige:cf对象和oc对象转化 __bridge_transfer:把cf对象转化成oc对象
                    //brige retain:oc转成cf对象
                    [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;
                }
    
                // 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);
                //reverseObjectEnumerator逆序
                // 倒序遍历
                //这里的证书链顺序是从叶节点到根节点
                for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
                    //如果我们的证书中,有一个和它证书链中的证书匹配的,就返回YES
                    // 是否本地包含相同的data
                    if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
                        return YES;
                    }
                }
                //没有匹配的
                return NO;
            }
                //公钥验证 AFSSLPinningModePublicKey模式同样是用证书绑定(SSL Pinning)方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据
     
            case AFSSLPinningModePublicKey: {
                NSUInteger trustedPublicKeyCount = 0;
                // 从serverTrust中取出服务器端传过来的所有可用的证书,并依次得到相应的公钥
                NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
                //遍历服务端公钥
                for (id trustChainPublicKey in publicKeys) {
                    //遍历本地公钥
                    for (id pinnedPublicKey in self.pinnedPublicKeys) {
                        //判断如果相同 trustedPublicKeyCount+1
                        if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
                            trustedPublicKeyCount += 1;
                        }
                    }
                }
                return trustedPublicKeyCount > 0;
            }
        }
        
        return NO;
    }
    

    相关文章

      网友评论

          本文标题:AFNetworking源码分析之安全策略

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