AFNetworking框架下Https請求失敗的一些總結(jié)(一)

最近公司項目的合作方陸續(xù)升級為https,本來是一件可喜可賀的事,但是樂極生悲,總有一些幺蛾子不那么讓人省心...

ERROR:NSURLErrorDomain error code -999

一、報錯信息解釋

定義:NSURLErrorCancelled = -999

即:從定義很容易看出,該錯誤說明當(dāng)前請求被取消了。

二、報錯原因

為什么會被取消呢?

理解一個東西:
NSURLSessionAuthChallengeDisposition (如何處理證書)

    NSURLSessionAuthChallengeUseCredential = 0, 使用該證書 安裝該證書    
    NSURLSessionAuthChallengePerformDefaultHandling = 1, 默認(rèn)采用的方式,該證書被忽略  
    NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2, 取消請求,證書忽略 
    NSURLSessionAuthChallengeRejectProtectionSpace = 3, 拒絕

再看一段代碼:

AFNetworking代碼片段

不難看出,如果evaluateServerTrust:forDomain方法返回NO,該請求就會被取消掉。下面是源碼(已加注釋):

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{
/*
1. allowInvalidCertificates:是否允許使用自建證書(服務(wù)器自己生成的CA證書)。
2. validatesDomainName:是否需要驗證domain。如果你想驗證自建證書的domain是否有效。那么你必須使用pinnedCertificates并且SSLPinningMode不為AFSSLPinningModeNone才可以。
3. SSLPinningMode:證書驗證方式。
   3.1. AFSSLPinningModeNone:表示不使用SSL pinning,無條件信任服務(wù)器證書。
   3.2. AFSSLPinningModeCertificate:驗證服務(wù)器返回證書和本地證書的所有部分。
   3.3. AFSSLPinningModePublicKey :驗證服務(wù)器返回證書和本地證書中的PublicKey部分。
4. 所以當(dāng)客戶端允許自建證書且需要驗證域名時,沒有導(dǎo)入的pinnedCertificates或者SSLPinningMode == AFSSLPinningModeNone,均表示無法驗證該自建證書。所以都返回NO。
*/
    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;
    }

// 設(shè)置驗證證書的策略
    NSMutableArray *policies = [NSMutableArray array];
    if (self.validatesDomainName) {
        [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
    } else {
        [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
    }

    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

// 驗證是否允許自建證書或者非自建證書是否驗證成功
    if (self.SSLPinningMode == AFSSLPinningModeNone) {
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        return NO;
    }

    switch (self.SSLPinningMode) {
        case AFSSLPinningModeNone:
        default:
            return NO;
        case AFSSLPinningModeCertificate: {
// 驗證證書
            NSMutableArray *pinnedCertificates = [NSMutableArray array];
            for (NSData *certificateData in self.pinnedCertificates) {
                [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
            }
            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;
            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;
}

通過源碼不難找出請求被取消的原因了。

三、 解決方法

  1. 方法一:對于一些沒有自己SDK的合作第三方,目前部分是采用自建證書的方式,且更新至https是分階段進(jìn)行,所以不可能及時將證書拷貝給到用戶,所以此時必須在項目中配置“允許自建”、“不需要驗證域名”等,否則請求會被取消,出現(xiàn)題中錯誤。答案來源
manager.securityPolicy.allowInvalidCertificates = YES;
manager.securityPolicy.validatesDomainName = NO;

或者

manager.securityPolicy.allowInvalidCertificates = YES;
manager.securityPolicy.SSLPinningModep == AFSSLPinningModeNone;
  1. 方法二:
    其余一些可能性
    來自github

四、資料參考

圖解SSL/TLS協(xié)議
SSL/TLS協(xié)議運行機(jī)制的概述
正確使用AFNetworking的SSL
AFNetworking源碼解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容