AFN中的鑒權(quán)

1. AFN 概覽

作為 iOS 上最知名且最廣泛使用的網(wǎng)絡(luò)庫,AFN 到底做了什么?

主要流程:

  1. 發(fā)起請(qǐng)求;
  2. 請(qǐng)求序列化;
  3. 安全策略應(yīng)用;
  4. 響應(yīng)序列化;
  5. 返回請(qǐng)求;

使用:

  1. 繼承 AFHTTPSessionManager,創(chuàng)建自定義的請(qǐng)求對(duì)象;
  2. 設(shè)置安全策略;
  3. 發(fā)送 post、get 請(qǐng)求;
  4. 回調(diào)中處理結(jié)果;
類結(jié)構(gòu)和請(qǐng)求流程

本文主要研究 AFN 的鑒權(quán)流程;

2. 鑒權(quán)的級(jí)別

鑒權(quán)按照級(jí)別分為會(huì)話級(jí)別的鑒權(quán)和任務(wù)級(jí)別的鑒權(quán)。會(huì)話建立之后,其鑒權(quán)適用于這個(gè) Session 發(fā)出的所有 Task,而每個(gè)不同的 Task 則可能會(huì)發(fā)出不同類型的鑒權(quán)。兩個(gè)級(jí)別最典型的代表是 TLS(HTTPS)鑒權(quán)和 Basic Challenge (基礎(chǔ)鑒權(quán))。HTTPS 鑒權(quán)通過單向驗(yàn)證或者雙向驗(yàn)證的方式完成鑒權(quán),而基礎(chǔ)鑒權(quán)一般用于訪問特定的文件或者預(yù),需要輸入用戶名和密碼;

常見鑒權(quán)級(jí)別的劃分,以及在 iOS 中對(duì)應(yīng)的常量:

認(rèn)證方式

在 HTTPS 未完全普及之前,HTTP 采用基礎(chǔ)認(rèn)證、摘要認(rèn)證、表單認(rèn)證、Window 統(tǒng)一認(rèn)證(NTLM/Kerberos)等方式來確認(rèn)身份。但是這些認(rèn)證方式都沒有 HTTPS 中的 SSL/TLS 認(rèn)證安全,所以隨著 HTTPS 的普及,這些認(rèn)證方法也基本不再使用;

3. AFN 鑒權(quán)級(jí)別的處理

iOS 中分為 Session 和 Task 級(jí)別的 didReceiveChallenge 方法,分別來執(zhí)行上文中涉及到的會(huì)話級(jí)別的認(rèn)證和任務(wù)級(jí)別的認(rèn)證:

OverView
Determine the Appropriate Delegate Method

總結(jié):

  1. Task 和 Session 的代理方法分別處理兩個(gè)級(jí)別的鑒權(quán)邏輯;
  2. Task 如果未實(shí)現(xiàn)則會(huì)調(diào)用 Session 的代理方法;

官方文檔:Handling an Authentication Challenge

而 AFN 通過 respondsToSelector 方法,根據(jù)業(yè)務(wù)層是否實(shí)現(xiàn) sessionDidReceiveAuthenticationChallenge 這個(gè) block 來決定是否需要調(diào)用 Session 級(jí)別的 didReceiveChallenge 方法,為用戶自定義 Session 級(jí)別的鑒權(quán)預(yù)留了口子。如果用戶沒有實(shí)現(xiàn),則將所有的鑒權(quán)類型在 Task 中來執(zhí)行:

session的處理

最終流程走向:

安全級(jí)別

既然 Task 的代理方法未實(shí)現(xiàn)時(shí),都會(huì)走到 Session 的代理方法,那為何 AFN 還要多此一舉將所有的鑒權(quán)在 Task 的代理方法中進(jìn)行呢?可能兩個(gè)代理方法的調(diào)用場景和預(yù)期不一樣?暫未驗(yàn)證,不保證正確性~~~

4. 自定義認(rèn)證邏輯(基礎(chǔ)認(rèn)證)

基礎(chǔ)認(rèn)證即 realm 認(rèn)證,當(dāng)訪問的網(wǎng)站添加了基礎(chǔ)認(rèn)證時(shí),需要訪問者提供用戶名和密碼,此時(shí) challenge 中的對(duì)應(yīng)的枚舉類型為 NSURLAuthenticationMethodHTTPBasic

隨著證書成本的降低, TLS 的普及,當(dāng)前 HTTP 中都使用 TLS 認(rèn)證,即:HTTPS。但是 AFN 仍然通過 authenticationChallengeHandler 保留了基礎(chǔ)認(rèn)證的邏輯的邏輯,從 AFN 的注釋中可以了解到,AFN 會(huì)根據(jù) handler 的 return 值來進(jìn)行四種處理邏輯:

自定義認(rèn)證邏輯
  1. return nil:此時(shí)該種類的認(rèn)證邏輯全權(quán)交由業(yè)務(wù)上層處理,AFN 不負(fù)責(zé) didReceiveChallenge 代理方法中 completionHandler(disposition, credential) 的調(diào)用,業(yè)務(wù)層需要自己調(diào)用。此種情況適用于業(yè)務(wù)層需要彈出交互 UI 供用戶填寫認(rèn)證信息(用戶名、密碼)的場景,因?yàn)槭钱惒降模员仨殬I(yè)務(wù)層調(diào)用 completionHandler;

  2. return error:業(yè)務(wù)層處理此時(shí)該種類的認(rèn)證時(shí)出錯(cuò),AFN 直接調(diào)用completionHandler(cancel, nil) 進(jìn)行認(rèn)證的取消操作;

  3. return NSURLCredential:此時(shí) AFN 會(huì)將 Credential(包含認(rèn)證信息,如用戶名和密碼) 上傳以供服務(wù)端認(rèn)證。該場景適用于客戶端擁有認(rèn)證信息,不需要同用戶進(jìn)行交互的場景;

  4. return NSNumber:此時(shí)會(huì)根據(jù) disposition 的類型進(jìn)行處理,如果是 Default 且為單向認(rèn)證中的客戶端認(rèn)證服務(wù)端,則會(huì)進(jìn)入 securityPolicy 的認(rèn)證邏輯,否則會(huì)直接取消或者拒絕認(rèn)證;

如果未實(shí)現(xiàn) authenticationChallengeHandler ,則 AFN 全權(quán)根據(jù) securityPolicy 中的策略來處理認(rèn)證。需要注意的是 AFN 只實(shí)現(xiàn)了單向認(rèn)證并和鎖定認(rèn)證配合進(jìn)行;

自定義認(rèn)證處理時(shí)首先需要通過 challenge.protectionSpace.authenticationMethod 來判斷 challenge 的類型是否是自己需要處理的類型;

基礎(chǔ)認(rèn)證相關(guān)文章:Nginx配置基礎(chǔ)認(rèn)證

5. 無效證書

AFN 中通過 allowInvalidCertificates 參數(shù)讓上層決定是否信任無效證書,如過期、吊銷、證書鏈無效等。在 AFN 接管鑒權(quán)流程時(shí),存在兩種場景:

  1. 單向認(rèn)證(無鎖定模式)

此時(shí),在客戶端對(duì)服務(wù)端進(jìn)行校驗(yàn)時(shí),如果 allowInvalidCertificates 為 NO,對(duì)鑒權(quán)邏輯影響不大。如果為 YES,則會(huì)無條件信任服務(wù)端的證書,其邏輯大概等同以下代碼:

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        disposition = NSURLSessionAuthChallengeUseCredential;
        credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        completionHandler(disposition, credential);
    } 
    ...其他代碼...
}
  1. 鎖定模式

此時(shí)如果 allowInvalidCertificates = YES,則會(huì)直接進(jìn)入鎖定認(rèn)證的邏輯。如果為 NO,則先按照 securityPolicy 中既定的策略對(duì)服務(wù)端證書進(jìn)行校驗(yàn),如果失敗則按照證書校驗(yàn)失敗的結(jié)果返回,既定策略校驗(yàn)成功則繼續(xù)鎖定認(rèn)證,其代碼如下:

if (self.SSLPinningMode == AFSSLPinningModeNone) {
    // 非證書鎖定認(rèn)證(不需要業(yè)務(wù)層提供證書)
    // 如果允許無效證書則直接返回YES
    // 不允許無效證書,則根據(jù)上文中的policy走證書認(rèn)證的流程
    return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!self.allowInvalidCertificates && !AFServerTrustIsValid(serverTrust)) {
    // 需要進(jìn)行證書有效性校驗(yàn),且校驗(yàn)失敗
    return NO;
}
...后面才開始鎖定認(rèn)證邏輯...

需要注意的是:

  • 是否驗(yàn)證域名和是否為證書鎖定認(rèn)證模式?jīng)]有任何關(guān)系,只是 Apple 規(guī)定/建議,如果是自簽名證書且要認(rèn)證域名,則應(yīng)當(dāng)使用鎖定證書的模式進(jìn)行認(rèn)證;

而 AFN 對(duì) Apple 的該邏輯進(jìn)行了實(shí)現(xiàn);

具體注釋:

AFN自簽名證書注釋

上述代碼發(fā)生的情況:

  1. 存在域名且需要驗(yàn)證域名;
  2. allowInvalidCertificates = YES,即當(dāng)前采用自簽名證書,無需對(duì)信任鏈進(jìn)行驗(yàn)證;
  3. 不需要進(jìn)行鎖定認(rèn)證;

而 Apple 和 AFN 都規(guī)定,如果使用自簽名證書,則應(yīng)當(dāng)將自己的鎖定證書添加到信任鏈中,并采用鎖定認(rèn)證模式進(jìn)行驗(yàn)證,否則就會(huì)走入上述代碼,從而引發(fā)報(bào)錯(cuò);

6. 單向認(rèn)證

對(duì)于單項(xiàng)認(rèn)證,AFN 只做了一個(gè)工作:是否驗(yàn)證域名。這個(gè)功能通過 validateDomainName 來設(shè)置,默認(rèn)為 YES;

AFN 早期版本默認(rèn)是 X509 認(rèn)證,即只驗(yàn)簽,不認(rèn)證域名,存在證書被劫持/替換的風(fēng)險(xiǎn),該重大漏洞被曝出后 AFN 對(duì)其進(jìn)行了修復(fù),這里不再贅述。

我們可以通過 SecTrustCopyPolicies 方法在 AFN 調(diào)用 SecTrustSetPolicies 之前獲取 SecTrustRef 中默認(rèn)的認(rèn)證策略來確定 Apple 中的默認(rèn)認(rèn)證策略是否包含域名校驗(yàn),代碼如下:

CFArrayRef defaultPolicies = NULL;
// 默認(rèn)策略
SecTrustCopyPolicies(serverTrust, &defaultPolicies);
// x509策略(不驗(yàn)證域名)
SecPolicyRef x509Policy = SecPolicyCreateBasicX509();

結(jié)果:

默認(rèn)認(rèn)證策略

x509 策略:

x509策略

總結(jié):x509 策略相對(duì)簡單,不驗(yàn)證域名,而默認(rèn)的策略為 SSLPolicy 會(huì)校驗(yàn)請(qǐng)求的域名和證書的域名是否一致;

這里可以看到,Apple 在系統(tǒng)層面已經(jīng)實(shí)現(xiàn)了大多數(shù)證書校驗(yàn)的邏輯,并不需要開發(fā)者去里學(xué)習(xí)復(fù)雜的證書體系和 OpenSSL 等工具;

7. 鎖定認(rèn)證

鎖定認(rèn)證的概念不再贅述,詳見:iOS中的HTTPS認(rèn)證 中的第三章節(jié);

另外再重申,AFN 只實(shí)現(xiàn)了單向認(rèn)證中的客戶端對(duì)服務(wù)端證書的校驗(yàn)邏輯,所以 AFN 的鎖定認(rèn)證也是基于 [challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust] == YES 這個(gè)前提的,即:僅支持單向認(rèn)證下的鎖定認(rèn)證。

AFN 中的鎖定認(rèn)證可以用于

  1. 服務(wù)端使用了非正常的證書(如自簽名證書、過期、吊銷等):不想直接無腦信任服務(wù)端證書而不做任何校驗(yàn),所以使用鎖定認(rèn)證來保證安全;
  2. 服務(wù)端使用正常的證書(CA下發(fā)):避免客戶端證書鏈被污染,起到雙重保障;

猜測第一種情況是 AFN 實(shí)現(xiàn)鎖定認(rèn)證的主要原因,因?yàn)?Apple 建議如果是自簽名證書,最好不要直接信任,而是應(yīng)當(dāng)將自簽名證書添加到信任錨點(diǎn)中,所以 AFN 基于這一點(diǎn)實(shí)現(xiàn)了鎖定認(rèn)證模式,AFN 中的注釋如下:

AFN-自簽名證書

當(dāng)為第二種情況時(shí),先對(duì)證書按照 Policy 進(jìn)行校驗(yàn),校驗(yàn)通過再進(jìn)行鎖定認(rèn)證,代碼如下:

if (self.SSLPinningMode == AFSSLPinningModeNone) {
    // 非證書鎖定認(rèn)證(不需要業(yè)務(wù)層提供證書)
    // 如果允許無效證書則直接返回YES
    // 不允許無效證書,則根據(jù)上文中的policy走證書認(rèn)證的流程
    return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!self.allowInvalidCertificates && !AFServerTrustIsValid(serverTrust)) {
    // 需要進(jìn)行證書有效性校驗(yàn),且校驗(yàn)失敗
    return NO;
}
......鎖定認(rèn)證邏輯......

AFN 對(duì)鎖定認(rèn)證中的兩種模式進(jìn)行了實(shí)現(xiàn):

  1. 證書認(rèn)證(簽名認(rèn)證);
  2. 公鑰認(rèn)證;

8. 鎖定認(rèn)證-證書認(rèn)證(AFSSLPinningModeCertificate)

代碼:

// 1. 取出 App 內(nèi)嵌證書
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
    [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}

// 2. 將內(nèi)嵌證書設(shè)置成錨點(diǎn),忽略系統(tǒng)錨點(diǎn)
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

// 3. 使用新的錨點(diǎn)對(duì)證書進(jìn)行校驗(yàn)
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)
// 4. 獲取校驗(yàn)完畢之后的信任鏈
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);

// 5. 逆序取出信任鏈中的證書,如果信任鏈中任意證書和任意內(nèi)嵌證書相同,則校驗(yàn)通過
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
    if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
        return YES;
    }
}

return NO;
  1. 取出 App 內(nèi)嵌證書;
  2. 將內(nèi)嵌證書設(shè)置成錨點(diǎn),忽略系統(tǒng)錨點(diǎn);
  3. 使用新的錨點(diǎn)對(duì)證書進(jìn)行校驗(yàn);
  4. 獲取校驗(yàn)完畢之后的信任鏈;
  5. 逆序取出信任鏈中的證書,如果信任鏈中任意證書和任意內(nèi)嵌證書相同,則校驗(yàn)通過;

這里的關(guān)鍵點(diǎn)有幾個(gè):

  1. 設(shè)置錨點(diǎn)

調(diào)用 SecTrustSetAnchorCertificates() 設(shè)置錨點(diǎn)之后,如果不調(diào)用 SecTrustSetAnchorCertificatesOnly() ,此時(shí)會(huì)忽略原本的信任錨點(diǎn),即系統(tǒng)內(nèi)嵌的 Root CA 證書都會(huì)忽略。所以這就達(dá)到了只信任部分證書集合的目的,規(guī)避掉了用戶的信任鏈被污染的情況,提高了安全性。

另外,自簽名證書使用系統(tǒng)默認(rèn)的錨點(diǎn),也就是系統(tǒng)內(nèi)置的 RootCA 證書校驗(yàn)時(shí)是無法成功的,設(shè)置內(nèi)嵌證書稱為錨點(diǎn)之后才能成功;

  1. 對(duì)比信任鏈

從代碼可以看出,設(shè)置錨點(diǎn)并校驗(yàn)通過后,拿到了信任鏈。然后循環(huán)取出信任鏈中的證書,如果有一個(gè)證書存在于內(nèi)嵌證書數(shù)組中,則校驗(yàn)通過;

那么,這里有兩個(gè)問題:

  1. 校驗(yàn)成功的標(biāo)準(zhǔn)/流程是什么?
  2. 信任鏈?zhǔn)鞘裁矗?/li>

關(guān)于第一點(diǎn):
證書校驗(yàn)成功的標(biāo)準(zhǔn)有幾個(gè):

  1. 葉子節(jié)點(diǎn)域名和請(qǐng)求的域名一致;
  2. 葉子節(jié)點(diǎn)到根節(jié)點(diǎn)之間層層簽發(fā),父節(jié)點(diǎn)的公鑰對(duì)子節(jié)點(diǎn)的簽名驗(yàn)簽成功;
  3. 證書鏈的根節(jié)點(diǎn)存在于錨點(diǎn)證書列表中;

其中,第三個(gè)驗(yàn)證規(guī)則中,一般而言根節(jié)點(diǎn)的證書是 Root CA 的證書,被嵌入到系統(tǒng)或者瀏覽器中,這些證書是自簽名的。但是驗(yàn)證的過程中不一定會(huì)對(duì)這個(gè)證書進(jìn)行驗(yàn)簽(使用證書中自身公鑰去驗(yàn)簽自身的簽名),更多的應(yīng)該是對(duì)比操作,即校驗(yàn)這些證書是否存在于系統(tǒng)的根證書庫中。

因此,猜測 AFServerTrustIsValid 中的系統(tǒng)函數(shù) SecTrustEvaluate 的最后一步的邏輯可能是:

  1. 默認(rèn)情況下會(huì)去系統(tǒng)的根證書庫中進(jìn)行證書對(duì)比,如果被校驗(yàn)的證書的根節(jié)點(diǎn)存在于證書庫中,則校驗(yàn)通過;
  2. 如果使用了 SecTrustSetAnchorCertificates 來重置錨點(diǎn)后,則該方法只校驗(yàn)證書鏈,而不去對(duì)比根證書;

因此,AFN 中才有了上述代碼中逆序?qū)Ρ葍?nèi)嵌證書列表的操作:

逆序?qū)Ρ葍?nèi)嵌證書

關(guān)于第二點(diǎn)(信任鏈?zhǔn)鞘裁矗?/p>

AFN 中獲取信任鏈的方法使用了 SecTrustGetCertificateCount() 函數(shù),而該函數(shù)在調(diào)用之前,如果未對(duì)證書進(jìn)行校驗(yàn),則會(huì)先執(zhí)行校驗(yàn)操作:

SecTrustGetCertificateCount

所以,能夠生成信任鏈中必定是按照第一點(diǎn)中的兩個(gè)規(guī)則校驗(yàn)成功的;

信任鏈的內(nèi)容則是校驗(yàn)的過程,舉個(gè)例子,對(duì)于只有一個(gè) intermediate CA 和 一個(gè) Root CA 參與簽發(fā)的證書而言:

  1. 如果錨點(diǎn)設(shè)置成 intermediate CA 證書,則證書鏈包含兩個(gè)證書:intermediate CA + User;
  2. 如果錨點(diǎn)證書直接設(shè)置成葉子證書(User),那么證書鏈就只包含一個(gè)證書:User;
  3. 如果使用系統(tǒng)內(nèi)嵌的證書列表作為錨點(diǎn),那么證書鏈就包含三個(gè)證書:Root CA + intermediate CA + User;

其中需要特別注意,調(diào)用 SecTrustEvaluateWithError()進(jìn)行驗(yàn)證時(shí),如果 intermediate CA 不存在與服務(wù)器傳送過來的證書中(即服務(wù)器只發(fā)送了葉子證書),那么驗(yàn)簽之前會(huì)先聯(lián)網(wǎng)下載中間證書,所以這個(gè)校驗(yàn)過程最好異步進(jìn)行;

這里,將上述三種情況的信任鏈打印出來:

  1. 系統(tǒng)內(nèi)嵌證書作為錨點(diǎn):
信任鏈-系統(tǒng)內(nèi)嵌證書作為錨點(diǎn)
  1. intermediate CA 的證書作為錨點(diǎn):
信任鏈-CA作為錨點(diǎn)
  1. 葉子證書作為錨點(diǎn):
葉子證書作為錨點(diǎn)

總之,該信任鏈?zhǔn)墙?jīng)過認(rèn)證的信任鏈節(jié)點(diǎn)集合,而不是服務(wù)器傳送過來的證書中證書的個(gè)數(shù)或者節(jié)點(diǎn);

所以,AFN 中第一次校驗(yàn)使用的是系統(tǒng)根證書作為錨點(diǎn),對(duì)證書進(jìn)行校驗(yàn)。重新設(shè)置錨點(diǎn)之后,使用的是內(nèi)嵌證書作為錨點(diǎn)進(jìn)行校驗(yàn),只要驗(yàn)證成功,因?yàn)殄^點(diǎn)證書不是驗(yàn)簽而是對(duì)比,那么信任鏈中必定包含內(nèi)嵌證書。

鎖定模式中的證書鎖定其實(shí)是簽名鎖定,即驗(yàn)證兩個(gè)證書的簽名是否一致。但是 AFN 中直接對(duì)比證書的二進(jìn)制是否相同,本質(zhì)上是一樣的,對(duì)比二進(jìn)制省去了簽名的提取,但是效率可能會(huì)降低吧;

9. 鎖定認(rèn)證-公鑰認(rèn)證(AFSSLPinningModePublicKey)

鎖定模式中,和證書公鑰認(rèn)證相對(duì)應(yīng)的就是公鑰認(rèn)證。因?yàn)樽C書內(nèi)容(如更新了有效期)一旦發(fā)生變化,簽名必定發(fā)生變化,但是其包含的公鑰不一定發(fā)生變化。比如用戶可以使用自己的秘鑰對(duì)多次申請(qǐng)同一個(gè)域名的證書。此時(shí),對(duì)比簽名的方式就需要在 App 中重新內(nèi)嵌新的證書,而如果是對(duì)比公鑰,則不需要。

總之,有幾種方法來減少 App 中內(nèi)嵌證書的更新:

  1. 盡量不要使用葉子節(jié)點(diǎn)作為錨點(diǎn)內(nèi)嵌;
  2. 盡量使用中間 CA 證書作為錨點(diǎn);
  3. 盡量多內(nèi)嵌一些常用的中間 CA 證書;
  4. 盡量使用公鑰鎖定認(rèn)證的模式;

公鑰認(rèn)證的邏輯相對(duì)簡單:

case AFSSLPinningModePublicKey: {
    // 1. 獲取服務(wù)器證書中的信任鏈
    NSUInteger trustedPublicKeyCount = 0;
    NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);

    // 2. 對(duì)比信任鏈和內(nèi)嵌證書,有一個(gè)公鑰相同則驗(yàn)證通過
    for (id trustChainPublicKey in publicKeys) {
        for (id pinnedPublicKey in self.pinnedPublicKeys) {
            if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
                trustedPublicKeyCount += 1;
            }
        }
    }
    return trustedPublicKeyCount > 0;
}

步驟:

  1. 獲取服務(wù)器證書中的信任鏈;
  2. 對(duì)比信任鏈和內(nèi)嵌證書,有一個(gè)公鑰相同則驗(yàn)證通過;

這里的信任鏈就是使用系統(tǒng)內(nèi)置 RootCA 進(jìn)行校驗(yàn)完成的證書鏈,一般包含 Root CA + Intermediate CA + User,三層。

只要信任鏈中有一個(gè)公鑰存在于內(nèi)嵌證書中,則證明這個(gè)證書是指定的 intermediate/Root CA 簽發(fā)的,或者是葉子證書;

這種方法可以直接把葉子證書內(nèi)嵌,這樣就能精準(zhǔn)對(duì)比證書。即使葉子證書過期,但只要使用的是同一個(gè)公鑰簽發(fā)的,那么就能對(duì)比成功。至于是否使用 intermediate CA 證書內(nèi)嵌,這個(gè)需要自己去權(quán)衡;

10. 總結(jié)

AFN 做了什么:

  1. 封裝方面:序列化、網(wǎng)絡(luò)狀態(tài)的獲取、圖片下載、圖片緩存;
  2. 安全方面:HTTPS認(rèn)證中,實(shí)現(xiàn)了 ATS 默認(rèn)策略,同時(shí)新增了鎖定認(rèn)證的兩種模式的實(shí)現(xiàn)、自簽名證書的校驗(yàn)(Apple建議不要直接信任自簽名證書,應(yīng)當(dāng)使用鎖定模式來驗(yàn)證自簽名證書);
  3. 其他方面:代碼優(yōu)化,如使用線程組分發(fā)請(qǐng)求等;

個(gè)人總結(jié):

  1. AFN 早期使用過,但是后期隨著 iOS 的成熟,業(yè)務(wù)場景變得復(fù)雜,很多公司都建立起了自己的網(wǎng)絡(luò)框架,所以整體上對(duì) AFN 的使用較少;
  2. 但是,很多公司的代碼是參考 AFN 完成或者是部分使用 AFN 源文件的。我對(duì)其中的網(wǎng)絡(luò)狀態(tài)類、序列化類、鑒權(quán)使用較多;
  3. 序列化類就是將請(qǐng)求或者響應(yīng)的參數(shù)按照 HTTP 協(xié)議進(jìn)行序列化,比如 form-data、mimeType 、header 等。其中 AFN 只支持單張圖片上傳,因?yàn)槠鋵?duì)于 form-data 的構(gòu)建代碼不是數(shù)組的形式。我再擴(kuò)展對(duì)象為數(shù)組之后,按照 HTTP 協(xié)議,構(gòu)建多個(gè) form-data 從而實(shí)現(xiàn)了多張圖片的上傳;
  4. 再到后面,項(xiàng)目只使用到了請(qǐng)求序列化的源文件,因?yàn)轫?xiàng)目中使用了 jce 協(xié)議,在請(qǐng)求時(shí)需要序列化,但是對(duì)響應(yīng)進(jìn)行解包時(shí)采用的是自己的邏輯,使用 C++ 來實(shí)現(xiàn),所以并不需要響應(yīng)序列化的源文件;
  5. 鑒權(quán)邏輯中,ATS 的鑒權(quán)邏輯基本能滿足大部分 App 的需求,而 AFN 實(shí)現(xiàn)了 ATS 的默認(rèn)邏輯,即域名檢測+證書鏈驗(yàn)證+TLS最低協(xié)議版本認(rèn)證+摘要算法最低版本認(rèn)證。AFN 在此基礎(chǔ)上還提供了更為安全的鎖定認(rèn)證模式,對(duì)其中的公鑰鎖定和證書摘要鎖定進(jìn)行了實(shí)現(xiàn);
  6. 另外,早期服務(wù)端存在較多的自簽名證書的情況,可以使用鎖定認(rèn)證的模式或者直接信任的模式來完成自簽名證書的驗(yàn)證,AFN 也是支持的;
  7. AFN 的安全級(jí)別上,有全局的設(shè)置??
  8. TAF 只做會(huì)話層的鑒權(quán),即沒有實(shí)現(xiàn) Task 的 didReceiveChallenge 代理方法;

即實(shí)現(xiàn)了下面幾種邏輯:

  1. 非正常證書-無條件信任;
  2. 非正常證書-鎖定認(rèn)證;
  3. 證書正常校驗(yàn)-無鎖定認(rèn)證;
  4. 證書正常校驗(yàn)-鎖定認(rèn)證;
  5. 證書正常交驗(yàn)時(shí)是否校驗(yàn)域名;
  6. 業(yè)務(wù)層全權(quán)接管;
  7. 業(yè)務(wù)層提供憑證(用戶名和密碼);

上述總結(jié)不需要過于糾結(jié),死摳邏輯和細(xì)節(jié)意義不大,只需要知道 AFN 實(shí)現(xiàn)的內(nèi)容還挺多,挺全,在自己的網(wǎng)絡(luò)框架中可以按需取用;

11. 一句話總結(jié)

AFN 中實(shí)現(xiàn)了 ATS 默認(rèn)的域名認(rèn)證,即需要校驗(yàn)域名、信任鏈、根證書是否存在于系統(tǒng)證書庫中等邏輯。并且實(shí)現(xiàn)了更簡單的 X509 認(rèn)證供開發(fā)者選擇。

與此同時(shí),AFN 想要實(shí)現(xiàn)自簽名證書的校驗(yàn)邏輯,但是 Apple 規(guī)定最好不要直接信任自簽名證書,而是應(yīng)當(dāng)將自簽名證書添加到錨點(diǎn)中進(jìn)行校驗(yàn)。AFN 則針對(duì)這個(gè)建議進(jìn)行了實(shí)現(xiàn),即實(shí)現(xiàn)了鎖定認(rèn)證模式中的簽名認(rèn)證。如此,無論是自簽名證書還是正常的證書,都可以添加鎖定模式進(jìn)行更安全的認(rèn)證,防止錨點(diǎn)證書列表被污染帶來的安全隱患。

另外,因?yàn)殒i定認(rèn)證中的簽名鎖定模式下,可能會(huì)存在證書頻繁更新的問題,此時(shí)就需要發(fā)包來解決。所以,AFN 又對(duì)鎖定認(rèn)證中的公鑰鎖定進(jìn)行了實(shí)現(xiàn),只對(duì)比證書中的公鑰,這就允許開發(fā)者使用同一個(gè)私鑰來申請(qǐng)多個(gè)證書,配合一些證書的使用規(guī)范,可以最大程度的規(guī)避因?yàn)樽C書更新而導(dǎo)致的發(fā)包流程。

最后,AFN 還實(shí)現(xiàn)了基礎(chǔ)認(rèn)證的兩種主要場景:如客戶端需要通過和用戶交互才能拿到用戶名密碼時(shí),將基礎(chǔ)認(rèn)證的邏輯完全交給業(yè)務(wù)層處理。當(dāng)客戶端無需交互就能拿到用戶名和密碼時(shí),AFN 提供了現(xiàn)成的方式供業(yè)務(wù)層使用。

最后編輯于
?著作權(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)容