概述
AFN框架中實現(xiàn)HTTPS請求的客戶端校驗是通過AFSecurityPolicy對象實現(xiàn)的,本篇主要分析一下AFSecurityPolicy的相關(guān)實現(xiàn)邏輯郎嫁。
TLS/SSL握手
HTTPS請求首先需要TLS/SSL握手浪慌,該協(xié)議也是建立在TCP基礎(chǔ)之上,以下是握手的幾個階段:
- 客戶端發(fā)出握手請求,請求報文主要包含協(xié)議版本號宏浩,客戶端提供的加密算法,一個隨機數(shù)random_Client廓俭。
- 服務(wù)端接收到請求云石,保存隨機數(shù)random_Client,然后發(fā)送響應(yīng)給客戶端研乒,包括選擇的加密算法汹忠、版本、壓縮算法雹熬、一個隨機數(shù)random_Server宽菜,以及證書鏈。
- 客戶端接收到信息竿报,將隨機數(shù)random_Server保存铅乡,并且對返回的證書鏈進行校驗,如果檢驗不通過烈菌,終止連接阵幸。如果校驗通過產(chǎn)生隨機數(shù)字Pre_master,并用證書中的公鑰進行加密芽世,將加密內(nèi)容發(fā)送給服務(wù)器挚赊。同時客戶端根據(jù)random_Client、random_Server和Pre_master通過相應(yīng)算法得到今后雙方通信的密鑰key济瓢∫螅客戶端邏輯結(jié)束。
- 服務(wù)端接收到公鑰加密的信息葬荷,通過證書的私鑰解密得到隨機數(shù)字Pre_master涨共,然后根據(jù)random_Client、random_Server和Pre_master通過算法得到今后雙方通信的密鑰key宠漩。
- 握手完畢举反,客戶端和服務(wù)端通過生成的密鑰key和之前約定的對稱加密算法對通信過程的報文數(shù)據(jù)進行加密。
在握手的過程中扒吁,密鑰數(shù)字的交換過程使用非對稱加密火鼻,且證書的私鑰保存在服務(wù)端,如果私鑰不泄露雕崩,正常情況下無法破解加密數(shù)據(jù)魁索。當最終密鑰生成,握手之后的數(shù)據(jù)傳輸用的是對稱加密盼铁,比一直使用非對稱加密性能提升粗蔚。
TLS/SSL握手的關(guān)鍵在于客戶端對服務(wù)器返回的證書進行驗證,比較有名的中間人攻擊就是通過偽造證書的方式竊取傳輸過程中加密的數(shù)據(jù)饶火。
證書校驗
SSL證書是數(shù)字證書的一種類型鹏控,專門用于HTTPS類型的網(wǎng)絡(luò)請求致扯,遵循X.509標準生成。SSL證書由CA(Certificate Authority)機構(gòu)負責頒發(fā)当辐,證書的申請流程如下:
- 申請者提供自己的必要信息(包括身份信息抖僵,公鑰、私鑰等)給CA機構(gòu)缘揪。
- CA機構(gòu)認證申請者的信息耍群。
- 認證通過后創(chuàng)建新證書,并通過哈希算法得到證書的摘要找筝,用自己證書中的私鑰加密摘要世吨,得到新證書的簽名。
下圖是訪問百度網(wǎng)站時呻征,下發(fā)的SSL證書:
可以看出baidu.com證書是由GlobalSign Organization Validation CA的機構(gòu)創(chuàng)建并頒發(fā)的耘婚,而它存在上一級CA機構(gòu),名稱是Global Root CA陆赋,GlobalSign Organization Validation CA的證書是由Global Root CA頒發(fā)的沐祷,且證書的簽名是通過Global Root CA的私鑰生成的。證書的機構(gòu)是鏈式的攒岛。通過上圖赖临,可以知道證書的內(nèi)容主要包括,證書持有者的身份信息灾锯、證書頒發(fā)這的身份信息兢榨、證書的有效期、證書的公鑰顺饮、加密算法類型吵聪、證書的簽名等。當TLS/SSL握手時兼雄,服務(wù)端返回證書鏈吟逝,客戶端校驗證書的流程如下:
- 驗證證書的有效期(是否過期)、身份信息等赦肋。
- 驗證證書的簽名块攒,首先用哈希算法計算證書的摘要1,然后用證書鏈的上一級證書的公鑰解密簽名佃乘,得到摘要2囱井,然后比較摘要1和摘要2是否相等。
- 驗證證書頒發(fā)者的合法性趣避,即驗證上一級證書的簽名庞呕,需要用再上一級證書的公鑰解密簽名,然后和哈希算法計算出的摘要進行比較鹅巍。遞歸驗證千扶,直到驗證根證書,由于根證書沒有上級證書骆捧,是最上級CA頒發(fā)的澎羞,是自簽名的。需要將根證書加入操作系統(tǒng)中作為信任證書敛苇。如果將證書鏈中某一級證書是被設(shè)置成了錨點證書妆绞,則被視為根證書。
其中任何一步流程出現(xiàn)問題枫攀,都會導致證書校驗失敗括饶。此外證書的地址和訪問服務(wù)端的地址不一致,也會校驗失敗来涨。
AFSecurityPolicy
在AFN框架中图焰,調(diào)用AFSecurityPolicy對象securityPolicy的evaluateServerTrust:forDomain:方法校驗,校驗的目標對象被封裝在SecTrustRef對象serverTrust中蹦掐,首先看一下AFSecurityPolicy的相關(guān)屬性:
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode; //校驗?zāi)J?@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates; //本地綁定的證書
@property (nonatomic, assign) BOOL allowInvalidCertificates; //是否允許無效證書
@property (nonatomic, assign) BOOL validatesDomainName; //是否驗證域名
SSLPinningMode是校驗證書的模式技羔,是枚舉類型,如下:
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone, //默認驗證方式
AFSSLPinningModePublicKey, //比較證書的公鑰
AFSSLPinningModeCertificate, //比較證書
};
校驗證書的方式有三種卧抗,其中AFSSLPinningModeNone表示按照上文的方式驗證證書鏈藤滥,除了這種方式,AF還提供了SSL Pinning的方式驗證社裆,該方式把服務(wù)端下發(fā)的證書預(yù)先保存在APP的bundle中拙绊,然后通過比較服務(wù)端下發(fā)的證書和本地證書是否相同來校驗證書。使用該方式的原因是CA機構(gòu)頒發(fā)的證書比較昂貴泳秀,一些企業(yè)或者個人不申請CA頒發(fā)的證書标沪,而是自己手動創(chuàng)建證書,用SSL Pinning的方式只要比較證書內(nèi)容一樣嗜傅,無需驗證證書的權(quán)威性谨娜。促成SSL Pinning使用的另一原因是大多數(shù)APP訪問的服務(wù)端域名相對固定,只需要將相應(yīng)證書導入本地bundle就行了磺陡。AFSSLPinningModeCertificate采用SSL Pinning的方式趴梢,首先驗證服務(wù)器證書的有效期(是否過期)、身份信息等币他,然后將該證書和bundle中證書進行比較坞靶,是否一致。AFSSLPinningModeCertificate同樣采用SSL Pinning的方式蝴悉,但是不驗證證書的有效期等信息彰阴,同時只是比較兩個證書的公鑰是否一致。采用SSL Pinning的方式拍冠,本地buundle中導入的證書數(shù)據(jù)由pinnedCertificates維護尿这。
AFSecurityPolicy還提供了允許無效證書驗證通過的開關(guān)allowInvalidCertificates簇抵,以及是否需要驗證證書域名的開關(guān)validatesDomainName。下面分析一下AFSecurityPolicy相關(guān)方法射众。
AFSecurityPolicy相關(guān)方法
首先調(diào)用AFSecurityPolicy的evaluateServerTrust:forDomain:方法碟摆,首先做了一個判斷:
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
return NO;
}
...
}
如果允許無效的證書,同時希望驗證證書的域名叨橱,則需要用SSL Pinning的方式驗證典蜕,即驗證證書的方式不能是AFSSLPinningModeNone,或者SSL Pinng需要本地導入證書罗洗,即pinnedCertificates數(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;
}
然后判斷驗證方式如果是AFSSLPinningModeNone且不允許無效證書,則調(diào)用AFServerTrustIsValid方法進行校驗贩绕。代碼注釋如下:
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out); //方法驗證
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out: //goto語句直接
return isValid;
}
通過系統(tǒng)方法SecTrustEvaluate校驗證書典奉,將校驗結(jié)果存儲在result中,同時通過__Require_noErr_Quiet宏來處理該方法返回error的情況:
#ifndef __Require_noErr_Quiet
#define __Require_noErr_Quiet(errorCode, exceptionLabel) \
do \
{ \
if ( __builtin_expect(0 != (errorCode), 0) ) \
{ \
goto exceptionLabel; \
} \
} while ( 0 )
#endif
如果該方法調(diào)用過程中失敗丧叽,即errorCode不為0卫玖,則通過goto語句跳轉(zhuǎn),isValid直接返回NO踊淳。如果該方法調(diào)用成功假瞬,則根據(jù)result來判斷isValid是否為YES。當值為kSecTrustResultUnspecified或者kSecTrustResultProceed時迂尝,驗證通過脱茉。
回到evaluateServerTrust:forDomain:方法中,接下來處理AFSSLPinningModeCertificate的情況垄开,代碼注釋如下:
case AFSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}//將本地證書加入數(shù)組
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates); //將本地證書設(shè)置為錨點證書
if (!AFServerTrustIsValid(serverTrust)) { //校驗證書
return NO;
}
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) { //本地證書數(shù)組中是否包含和服務(wù)端下發(fā)的證書內(nèi)容一樣的證書
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES; //如果包含琴许,則校驗通過
}
}
return NO; //否則不通過
}
因為導入APP Bundle中的證書不是CA頒發(fā)的,不受信任溉躲,所以調(diào)用SecTrustSetAnchorCertificates方法將先將這些證書設(shè)置為serverTrust證書鏈上的錨點證書榜田,類似于將這些證書設(shè)置為系統(tǒng)信任的根證書,然后調(diào)用AFServerTrustIsValid方法校驗serverTrust證書鏈時锻梳,如果遇到錨點證書箭券,則終止驗證。然后調(diào)用AFCertificateTrustChainForServerTrust方法獲取serverTrust的證書鏈serverCertificates疑枯,遍歷證書鏈直到發(fā)現(xiàn)本地證書pinnedCertificates中有內(nèi)容相同的證書辩块,服務(wù)端下發(fā)的證書在本地認可的證書范圍內(nèi),校驗成功,如果沒有則校驗失敗废亭。?
接下來處理AFSSLPinningModePublicKey的方式国章,代碼注釋如下:
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust); //獲取serverTrust證書鏈的公鑰
for (id trustChainPublicKey in publicKeys) { //匹配本地的證書公鑰和serverTrust的公鑰
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0; //匹配成功,校驗成功
}
該方法首先獲取serverTrust證書鏈的公鑰豆村,然后匹配本地的證書公鑰和serverTrust的公鑰液兽,本地的公鑰通過self.pinnedPublicKeys屬性維護,在之前設(shè)置本地證書的方法中獲得你画,注釋如下:
- (void)setPinnedCertificates:(NSSet *)pinnedCertificates {
_pinnedCertificates = pinnedCertificates;
if (self.pinnedCertificates) { //遍歷本地證書
NSMutableSet *mutablePinnedPublicKeys = [NSMutableSet setWithCapacity:[self.pinnedCertificates count]];
for (NSData *certificate in self.pinnedCertificates) {
id publicKey = AFPublicKeyForCertificate(certificate); //獲取證書的公鑰
if (!publicKey) {
continue;
}
[mutablePinnedPublicKeys addObject:publicKey];
}
self.pinnedPublicKeys = [NSSet setWithSet:mutablePinnedPublicKeys]; //存放在pinnedPublicKeys屬性中
} else {
self.pinnedPublicKeys = nil;
}
}
如果匹配成功抵碟,則返回校驗成功桃漾,否則失敗坏匪。匹配方法AFSecKeyIsEqualToKey調(diào)用isEqual:方法進行判斷。
總結(jié)
AFN框架的AFSecurityPolicy類為我們實現(xiàn)了HTTPS證書校驗的功能撬统,且同時支持三種方式校驗證書适滓,開發(fā)者可以根據(jù)不同情況進行選擇,如果是CA頒發(fā)的證書恋追,開發(fā)者不用做額外邏輯凭迹,使用起來十分方便。