欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

AFNetworking解析(四)

程序员文章站 2022-04-06 23:48:43
本文主要介绍和探索AFNetworking和后台服务器的交互,以及挑战认证部分: 挑战认证 关于挑战认证的详细解释: 1、这个是由于http和https的区别引起的,HTTPS协议是由S...

本文主要介绍和探索AFNetworking和后台服务器的交互,以及挑战认证部分:

挑战认证

关于挑战认证的详细解释:
1、这个是由于http和https的区别引起的,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
2、HTTPS是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
3、https协议中身份认证的部分是由数字证书来完成的,证书由公钥、证书主体、数字签名等内容组成,在客户端发起SSL请求后,服务端会将数字证书发给客户端,客户端会对证书进行验证,并获取用于秘钥交换的非对称密钥。
4、申请者拿到CA的证书并部署在网站服务器端,那浏览器发起握手接收到证书后,如何确认这个证书就是CA签发的呢?怎样避免第三方伪造这个证书?答案就是数字签名(digital signature)。数字签名是证书的防伪标签,目前使用最广泛的SHA-RSA(SHA用于哈希算法,RSA用于非对称加密算法)

下面我们就看一下在AFNetworking中是如何实现挑战认真的

AFNetworking中挑战认证的方法

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler;

函数说明:
服务器端在接受到用户的请求时,某些时候会需要验证客户段是否是正常用户(通常是在涉及到金钱交易和用户个人信息的时候),在决定是否返回数据。这种情况就是挑战认证,即双方进行公钥和私钥的验证。
(NSURLAuthenticationChallenge *challenge)。接收到挑战后,客户端要根据服务端传来的challenge来生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential *credential(disposition指定应对这个挑战的方法,而credential是客户端生成的挑战证书,注意只有challenge中认证方法为NSURLAuthenticationMethodServerTrust的时候,才需要生成挑战证书)。最后调用completionHandler回应服务器端的挑战

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    /**
     挑战认证的处理方式(枚举类型有三种)
     NSURLSessionAuthChallengeUseCredential  采用指定的证书(证书可能为nil)
     NSURLSessionAuthChallengePerformDefaultHandling  采用默认的处理方式 (似乎这个代理没有被实现,证书参数被忽视(即,没有证书))
     NSURLSessionAuthChallengeCancelAuthenticationChallenge 取消挑战认证
     NSURLSessionAuthChallengeRejectProtectionSpace 这一次的挑战被拒绝,下一次在进行尝试,忽略这一次的证书
     */
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;
// sessionDidReceiveAuthenticationChallenge自定义的应对服务器端挑战认证的方法
    if (self.sessionDidReceiveAuthenticationChallenge) {
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
//        NSURLAuthenticationMethodServerTrust 挑战认证方法(NSString)
//        也就是说服务器端需要客户端返回一个根据认证挑战的保护空间提供的信任(即challenge.protectionSpace.serverTrust)产生的挑战证书。
//        而这个证书就需要使用credentialForTrust:来创建一个NSURLCredential对象
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            // 基于客户端的安全策略来决定是否信任该服务器,不信任的话,也就没必要响应挑战(具体的判断方法后面会有详细解释)
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
//              创建挑战证书(挑战方式为UseCredential和PerformDefaultHandling都需要新建挑战证书)
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
//                确定挑战的方式
                if (credential) {
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
//                取消挑战
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }
//              完成挑战
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

验证证书是否正确的方法

// 根据severTrust和domain来检查服务器端发来的证书是否可信
// 其中SecTrustRef是用于对服务器端传来的X.509证书评估的
// 而我们都知道,数字证书的签发机构CA,在接收到申请者的资料后进行核对并确定信息的真实有效,然后就会制作一份符合X.509标准的文件。证书中的证书内容包含的持有者信息和公钥等都是由申请者提供的,而数字签名则是CA机构对证书内容进行hash(哈希)加密后得到的,而这个数字签名就是我们验证证书是否是有可信CA签发的数据。

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{
    /**
        self.allowInvalidCertificates 是否允许使用自建证书
     self.validatesDomainName   验证domain是否有效
     pinnedCertificates 就是在客户端保存服务器端颁发的证书
     AFSSLPinningModeNone 表示你不使用SSL pinning 只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书
     这种情况表示,你即想验证自己的CA证书,但是并没有服务器设定的证书([self.pinnedCertificates count] == 0)或者 你只是像浏览器一下返回官方颁布的认证(self.SSLPinningMode == AFSSLPinningModeNone)这和你的需求是不符的,所以不能通过验证
     */
    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
//        根据文档,你应该只相信自己提供的证书(自建证书,服务器端创建或者构造的CA证书,非官方颁布)
        //  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).
//        添加自己CA证书作为锚点证书
        //           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 使用SecPolicyCreateSSL函数创建验证策略,其中第一个参数为true表示验证整个SSL证书链,第二个参数传入domain,用于判断整个证书链上需要验证的节点表示的那个domain是否和此处传入domain一致
    if (self.validatesDomainName) {
        [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);
//           如果SSLPinningMode为 AFSSLPinningModeNone,表示你不使用SSL pinning,但是我允许自建证书,那么返回YES,或者使用AFServerTrustIsValid函数看看serverTrust是否可信任,如果信任,也返回YES
    if (self.SSLPinningMode == AFSSLPinningModeNone) {
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
        //        不允许自建证书,但是使用AFServerTrustIsValid验证返回的NO,所以不通过验证
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        return NO;
    }
//      SSLPinningMode证书的验证方式
    switch (self.SSLPinningMode) {
        case AFSSLPinningModeNone: //上面已经针对这个情况(即:自建证书)进行了判断,能通过验证的都已经返回,补充判断
        default:
            return NO;
            // 这个模式表示用证书绑定(SSL Pinning)方式验证证书,需要客户端保存有服务端的证书拷贝
            // 注意客户端保存的证书存放在self.pinnedCertificates中
        case AFSSLPinningModeCertificate: {
            NSMutableArray *pinnedCertificates = [NSMutableArray array];
            for (NSData *certificateData in self.pinnedCertificates) {
//              这里使用SecCertificateCreateWithData函数对原先的pinnedCertificates做一些处理,保证返回的证书都是DER编码的X.509证书
                [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
            }
// 将pinnedCertificates设置成需要参与验证的Anchor Certificate(锚点证书,通过SecTrustSetAnchorCertificates设置了参与校验锚点证书之后,假如验证的数字证书是这个锚点证书的子节点,即验证的数字证书是由锚点证书对应CA或子CA签发的,或是该证书本身,则信任该证书),具体就是调用SecTrustEvaluate来验证
            SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

            if (!AFServerTrustIsValid(serverTrust)) {
                return NO;
            }
//          服务器端的证书链,注意此处返回的证书链顺序是从叶节点到根节点
//            获得链式验证后,固定证书应该包含在过去的位置(例如它的根CA)
            // 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]) {
                if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
                    return YES;
                }
            }

            return NO;
        }
//            AFSSLPinningModePublicKey模式同样是用证书绑定(SSL Pinning)方式验证,客户端要有服务端的证书拷贝,只验证证书里的公钥,不验证证书的有效期等信息
        case AFSSLPinningModePublicKey: {
            NSUInteger trustedPublicKeyCount = 0;
//            取出服务器端传过来的所有可用的证书,并依次得到相应的公钥
            NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
//            依次遍历公钥,如果和客户端绑定证书的公钥一致,那么就给trustedPublicKeyCount加一
            for (id trustChainPublicKey in publicKeys) {
                for (id pinnedPublicKey in self.pinnedPublicKeys) {
                    if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
                        trustedPublicKeyCount += 1;
                    }
                }
            }
//            如果>0说明通过过验证
            return trustedPublicKeyCount > 0;
        }
    }

    return NO;
}

这里面涉及到了AFNetworking封装的一些,验证证书的方法,下面拿出一些比较重要的方法进行介绍

方法一:

static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
    BOOL isValid = NO;//证书是否有效,默认为NO
    SecTrustResultType result;
    /**
         __Require(<#assertion#>, <#exceptionLabel#>)
     errorCode 代表SecTrustEvaluate函数的返回值
     此函数的含义为,errorCode不为0则开始下一步执行,此处的_out为,退出判断函数
     #ifndef __Require_noErr_Quiet
     #define __Require_noErr_Quiet(errorCode, exceptionLabel)                      \
     do                                                                          \
     {                                                                           \
     if ( __builtin_expect(0 != (errorCode), 0) )                            \
     {                                                                       \
              goto exceptionLabel;                                                \
     }                                                                       \
     } while ( 0 )
     #endif
     */
    __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
//  如果通过SecTrustEvaluate验证,则根据result再次进行验证
//    SecTrustResultType result枚举值
//    kSecTrustResultUnspecified 评估成功,此证书也被默认信任
//    kSecTrustResultProceed 评估成功,用户认定信任此证书
    isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);

_out:
    return isValid;
}

方法二:

//取出serverTrust中证书链上每个证书的公钥,并返回对应的该组公钥

static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
//    获取到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;
//        生成trust对象
        __Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out);
//        验证trust对象
        SecTrustResultType result;
        __Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out);
//        如果trust对象通过验证(格式复合X509证书格式)获取trust的公钥,并添加到trustChain中
        [trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];

    _out://验证失败
//        释放相应对象
        if (trust) {
            CFRelease(trust);
        }

        if (certificates) {
            CFRelease(certificates);
        }

        continue;
    }
    CFRelease(policy);
//    返回公钥数组
    return [NSArray arrayWithArray:trustChain];
}

基本AFNetwoking中主要的挑战认证也就这些关键方法了,这些挑战认证也就是传说中的安全认证。。。

下面说一下本地化证书的读取方式

/**
 *  读取证书
 *
 *  @return 返回RDRSA对象
 */
- (instancetype)init {
    self = [super init];
    //在工程里添加public_key.der时要在targets->build Phases - >Copy bundle Resources中添加
    NSString *publicKeyPath = [[NSBundle mainBundle] pathForResource:@"public_key.der"
                                                              ofType:@""];
    if (publicKeyPath == nil) {
        NSLog(@"Can not find pub.der");
        return nil;
    }
//    获取证书数据
    NSDate *publicKeyFileContent = [NSData dataWithContentsOfFile:publicKeyPath];
    if (publicKeyFileContent == nil) {
        NSLog(@"Can not read from pub.der");
        return nil;
    }
//    获取证书
//    NSData *keyData = [[NSData alloc]initWithBase64EncodedString:RSA_KEY options:0];
    certificate = SecCertificateCreateWithData(kCFAllocatorDefault, ( __bridge CFDataRef)publicKeyFileContent);
    if (certificate == nil) {
        NSLog(@"Can not read certificate from pub.der");
        return nil;
    }
//    获取策略
    policy = SecPolicyCreateBasicX509();
//    验证证书
    OSStatus returnCode = SecTrustCreateWithCertificates(certificate, policy, &trust);
    if (returnCode != 0) {
        NSLog(@"SecTrustCreateWithCertificates fail. Error Code: %d", (int)returnCode);
        return nil;
    }
    SecTrustResultType trustResultType;
    returnCode = SecTrustEvaluate(trust, &trustResultType);
    if (returnCode != 0) {
        NSLog(@"SecTrustEvaluate fail. Error Code: %d", (int)returnCode);
        return nil;
    }
//    获取公钥
    publicKey = SecTrustCopyPublicKey(trust);
    if (publicKey == nil) {
        NSLog(@"SecTrustCopyPublicKey fail");
        return nil;
    }
    maxPlainLen = SecKeyGetBlockSize(publicKey) - 12;
    return self;
}

二进制数据转16进制

/**
 *  将NSData转十六进制
 *
 *  @param data 二进制数据
 *
 *  @return 返回十六进制字符串
 */
- (NSString *)hexStringFromData:(NSData *)data{

    Byte *bytes = (Byte *)[data bytes];
    //下面是Byte 转换为16进制。
    NSString *hexStr=@"";
    for(int i=0;i<[data length];i++)
    {
        NSString *newHexStr = [NSString stringWithFormat:@"%x",bytes[i]&0xff];///16进制数

        if([newHexStr length]==1)

            hexStr = [NSString stringWithFormat:@"%@0%@",hexStr,newHexStr];

        else

            hexStr = [NSString stringWithFormat:@"%@%@",hexStr,newHexStr];
    }
    return hexStr;
}

通过公钥加密数据

- (NSData *) encryptWithData:(NSData *)content {
    size_t plainLen = [content length];
    if (plainLen > maxPlainLen) {
        NSLog(@"content(%ld) is too long, must < %ld", plainLen, maxPlainLen);
        return nil;
    }
    void *plain = malloc(plainLen);
    [content getBytes:plain
               length:plainLen];
    size_t cipherLen = 128; // 当前RSA的密钥长度是128字节
    void *cipher = malloc(cipherLen);
    //kSecPaddingNone!!!!!!
    OSStatus returnCode = SecKeyEncrypt(publicKey, kSecPaddingNone, plain,
                                        plainLen, cipher, &cipherLen);
    NSData *result = nil;
    if (returnCode != 0) {

        NSLog(@"SecKeyEncrypt fail. Error Code: %d", (int)returnCode);
    }
    else {
        result = [NSData dataWithBytes:cipher
                                length:cipherLen];

    }
    free(plain);
    free(cipher);
    return result;
}