AFNetwork處理https自建證書網(wǎng)絡(luò)請(qǐng)求原理

前言

最近項(xiàng)目里一部分接口用到了服務(wù)端的自建證書, 之前一直是用AFNetwork來處理相關(guān)的邏輯, 第一次接觸到https證書認(rèn)證相關(guān)邏輯, 寫篇小筆記記錄下

https在iOS的具體體現(xiàn) (單向驗(yàn)證)

在整個(gè)https通訊流程中, 對(duì)于app來說, 關(guān)鍵點(diǎn)其實(shí)就是公鑰的認(rèn)證. 確保當(dāng)前通訊的服務(wù)端所下發(fā)的公鑰與公司所在host的服務(wù)端是同一個(gè)證書所導(dǎo)出的公鑰, 才開始使用該公鑰來加密傳輸數(shù)據(jù).

AFNetworking要使用自建證書的代碼邏輯

先上處理代碼, 要使用自建證書時(shí), 把證書的數(shù)據(jù)放入AFHTTPSessionManager單例

//cerPath為證書在bundle里的路徑
NSData *certData = [NSData dataWithContentsOfFile:cerPath];

SecCertificateRef httpBinCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(certData));
NSSet *certSet = [[NSSet alloc] initWithObjects:(__bridge_transfer NSData *)SecCertificateCopyData(httpBinCertificate), nil];
    
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
// 是否允許,NO-- 不允許無效的證書
[securityPolicy setAllowInvalidCertificates:NO];
// 設(shè)置證書
[securityPolicy setPinnedCertificates:certSet];
[AFHTTPSessionManager manager].securityPolicy = securityPolicy;

核心方法

如果是用的自建證書是無法通過系統(tǒng)的默認(rèn)認(rèn)證規(guī)則的, 這時(shí)https連接就會(huì)被中斷, 如果要令自建證書也可以建立https連接, 就需要實(shí)現(xiàn)以下這個(gè)代理方法

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

如果你響應(yīng)這個(gè)方法, 系統(tǒng)會(huì)將本次TCP鏈接中的證書數(shù)據(jù)通過這個(gè)代理方法中的challenge參數(shù)拋給你, 當(dāng)你驗(yàn)證完成后, 通過調(diào)用completionHandler回調(diào)告知系統(tǒng)你的驗(yàn)證結(jié)果

AFNetworking處理自建證書的邏輯

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    if (self.sessionDidReceiveAuthenticationChallenge) {
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                if (credential) {
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

這里的關(guān)鍵是你驗(yàn)證證書后通過completionHandler告知系統(tǒng)你的驗(yàn)證結(jié)果, 該block回調(diào)第一個(gè)參數(shù)就是你的驗(yàn)證結(jié)果

typedef NS_ENUM(NSInteger, NSURLSessionAuthChallengeDisposition) {
    NSURLSessionAuthChallengeUseCredential = 0,                                       /* Use the specified credential, which may be nil */
    NSURLSessionAuthChallengePerformDefaultHandling = 1,                              /* Default handling for the challenge - as if this delegate were not implemented; the credential parameter is ignored. */
    NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2,                       /* The entire request will be canceled; the credential parameter is ignored. */
    NSURLSessionAuthChallengeRejectProtectionSpace = 3,                               /* This challenge is rejected and the next authentication protection space should be tried; the credential parameter is ignored. */
} 

這些結(jié)果枚舉基本能望文生義就不多做解釋了, completionHandler第二個(gè)參數(shù)一般就是把challenge里的證書對(duì)象回傳進(jìn)去, 這一步的關(guān)鍵是如何驗(yàn)證這個(gè)證書對(duì)象是合法的, 在afn中, 驗(yàn)證的邏輯放在這個(gè)方法里

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

AFNetworking處理https驗(yàn)證的幾種方式

證書里面包含里公鑰, SHA-256指紋等多種信息, 根據(jù)實(shí)際需要, 驗(yàn)證策略可以有所不同, 下面

二進(jìn)制數(shù)據(jù)比對(duì)
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]) {
          //直接比對(duì)NSData是否一致
        if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
            return YES;
        }
    }
    
    return NO;
}

最簡單粗暴的方式, 直接將證書轉(zhuǎn)成NSData進(jìn)行比對(duì), 完全一致就可以確定是同一張證書

公鑰比對(duì)
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;
}

只比對(duì)證書的公鑰, 畢竟只要公鑰沒被修改, 即使傳輸了數(shù)據(jù), 對(duì)方也沒有對(duì)應(yīng)的私鑰可以解密你的數(shù)據(jù), 相對(duì)沒那么嚴(yán)謹(jǐn)?shù)尿?yàn)證方式, 由于證書鏈里會(huì)有個(gè)多證書, 注意afn里面的邏輯是主要證書鏈上有任意一個(gè)節(jié)點(diǎn)的證書公鑰與self.pinnedPublicKeys這個(gè)受信任的公鑰是相同時(shí), 就會(huì)認(rèn)為這次認(rèn)證是成功的

疑問

看完之后還是有幾點(diǎn)小疑問

第一是如果證書里面帶有該證書的SHA-256簽名, 那是不是等于不需要在客戶端預(yù)埋證書呢? 只需要寫死SHA-256的值, 比對(duì)服務(wù)端下發(fā)的證書的SHA-256是否一致就可以提取公鑰了.

第二個(gè)是錨點(diǎn)證書的問題, 在afn里如果使用自建證書, afn會(huì)幫你將該自建證書設(shè)置為錨點(diǎn)證書. 但其實(shí)注釋了錨點(diǎn)證書的設(shè)置也一樣能建立起https連接, 那設(shè)置錨點(diǎn)證書的意義在哪呢?

待續(xù)...

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

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

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