前言:AFSecurityPolicy基于系統(tǒng)API? Security框架
1.AFN框架中實(shí)現(xiàn)HTTPS請(qǐng)求的客戶端校驗(yàn)是通過AFSecurityPolicy對(duì)象實(shí)現(xiàn)的? ?
2.Security框架用于保證應(yīng)用程序所管理之?dāng)?shù)據(jù)的安全普泡。該框架提供的接口可用于管理證書像寒、公鑰遍搞、私鑰以及信任策略嘉汰。它支持生成加密的安全偽隨機(jī)數(shù)斟冕。同時(shí)螃宙,它也支持對(duì)證書和Keychain密鑰進(jìn)行保存惶傻,是用戶敏感數(shù)據(jù)的安全倉庫嚼摩。
HTTPS請(qǐng)求首先需要TLS/SSL握手
客戶端發(fā)出握手請(qǐng)求灶体,請(qǐng)求報(bào)文主要包含協(xié)議版本號(hào)阅签,客戶端提供的加密算法,一個(gè)隨機(jī)數(shù)random_Client蝎抽。
服務(wù)端接收到請(qǐng)求愉择,保存隨機(jī)數(shù)random_Client,然后發(fā)送響應(yīng)給客戶端织中,包括選擇的加密算法锥涕、版本、壓縮算法狭吼、一個(gè)隨機(jī)數(shù)random_Server层坠,以及證書鏈。
客戶端接收到信息刁笙,將隨機(jī)數(shù)random_Server保存破花,并且對(duì)返回的證書鏈進(jìn)行校驗(yàn),如果檢驗(yàn)不通過疲吸,終止連接座每。如果校驗(yàn)通過產(chǎn)生隨機(jī)數(shù)字Pre_master,并用證書中的公鑰進(jìn)行加密摘悴,將加密內(nèi)容發(fā)送給服務(wù)器峭梳。同時(shí)客戶端根據(jù)random_Client、random_Server和Pre_master通過相應(yīng)算法得到今后雙方通信的密鑰key蹂喻〈型郑客戶端邏輯結(jié)束。
服務(wù)端接收到公鑰加密的信息口四,通過證書的私鑰解密得到隨機(jī)數(shù)字Pre_master孵运,然后根據(jù)random_Client、random_Server和Pre_master通過算法得到今后雙方通信的密鑰key蔓彩。
握手完畢治笨,客戶端和服務(wù)端通過生成的密鑰key和之前約定的對(duì)稱加密算法對(duì)通信過程的報(bào)文數(shù)據(jù)進(jìn)行加密驳概。
TLS/SSL握手的關(guān)鍵在于客戶端對(duì)服務(wù)器返回的證書進(jìn)行驗(yàn)證,比較有名的中間人攻擊就是通過偽造證書的方式竊取傳輸過程中加密的數(shù)據(jù)旷赖。
證書校驗(yàn)
SSL證書是數(shù)字證書的一種類型顺又,專門用于HTTPS類型的網(wǎng)絡(luò)請(qǐng)求,遵循X.509標(biāo)準(zhǔn)生成杠愧。SSL證書由CA(Certificate Authority)機(jī)構(gòu)負(fù)責(zé)頒發(fā),證書的申請(qǐng)流程如下:
申請(qǐng)者提供自己的必要信息(包括身份信息逞壁,公鑰流济、私鑰等)給CA機(jī)構(gòu)。
CA機(jī)構(gòu)認(rèn)證申請(qǐng)者的信息腌闯。
認(rèn)證通過后創(chuàng)建新證書绳瘟,并通過哈希算法得到證書的摘要,用自己證書中的私鑰加密摘要姿骏,得到新證書的簽名糖声。
當(dāng)TLS/SSL握手時(shí),服務(wù)端返回證書鏈分瘦,客戶端校驗(yàn)證書的流程如下:
1.驗(yàn)證證書的有效期(是否過期)蘸泻、身份信息等。
2.驗(yàn)證證書的簽名嘲玫,首先用哈希算法計(jì)算證書的摘要1悦施,然后用證書鏈的上一級(jí)證書的公鑰解密簽名,得到摘要2去团,然后比較摘要1和摘要2是否相等抡诞。
3.驗(yàn)證證書頒發(fā)者的合法性,即驗(yàn)證上一級(jí)證書的簽名土陪,需要用再上一級(jí)證書的公鑰解密簽名昼汗,然后和哈希算法計(jì)算出的摘要進(jìn)行比較。遞歸驗(yàn)證鬼雀,直到驗(yàn)證根證書顷窒,由于根證書沒有上級(jí)證書,是最上級(jí)CA頒發(fā)的源哩,是自簽名的蹋肮。需要將根證書加入操作系統(tǒng)中作為信任證書。如果將證書鏈中某一級(jí)證書是被設(shè)置成了錨點(diǎn)證書璧疗,則被視為根證書坯辩。
AFSecurityPolicy
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode; //校驗(yàn)?zāi)J?/p>
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates; //本地綁定的證書
@property (nonatomic, assign) BOOL allowInvalidCertificates; //是否允許無效證書
@property (nonatomic, assign) BOOL validatesDomainName; //是否驗(yàn)證域名
枚舉
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
? ? AFSSLPinningModeNone, //默認(rèn)驗(yàn)證方式
? ? AFSSLPinningModePublicKey, //比較證書的公鑰
? ? AFSSLPinningModeCertificate, //比較證書
};
校驗(yàn)證書的方式有三種:
1.AFSSLPinningModeNone表示按照上文的方式驗(yàn)證證書鏈
2.AF還提供了SSL Pinning的方式驗(yàn)證,該方式把服務(wù)端下發(fā)的證書預(yù)先保存在APP的bundle中崩侠,然后通過比較服務(wù)端下發(fā)的證書和本地證書是否相同來校驗(yàn)證書漆魔。AFSSLPinningModeCertificate采用SSL Pinning的方式,首先驗(yàn)證服務(wù)器證書的有效期(是否過期)、身份信息等改抡,然后將該證書和bundle中證書進(jìn)行比較矢炼,是否一致。
3.AFSSLPinningModeCertificate同樣采用SSL Pinning的方式阿纤,但是不驗(yàn)證證書的有效期等信息句灌,同時(shí)只是比較兩個(gè)證書的公鑰是否一致。采用SSL Pinning的方式欠拾,本地bundle中導(dǎo)入的證書數(shù)據(jù)由pinnedCertificates維護(hù)胰锌。
AFSecurityPolicy還提供了允許無效證書驗(yàn)證通過的開關(guān)allowInvalidCertificates,以及是否需要驗(yàn)證證書域名的開關(guān)validatesDomainName藐窄。下面分析一下AFSecurityPolicy相關(guān)方法资昧。
+ (NSSet*)certificatesInBundle:(NSBundle*)bundle;? 根據(jù)bundle的證書的初始化
+ (NSSet*)defaultPinnedCertificates; 默認(rèn)證書
+ (instancetype)defaultPolicy; AFSecurityPolicy普通初始化? ?AFSSLPinningModeNone;
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet*)pinnedCertificates 根據(jù)pinnedCertificates證書與AFSSLPinningMode 初始化
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;根據(jù)bundle證書與pinningMode初始化
- (void)setPinnedCertificates:(NSSet*)pinnedCertificates;? set方法
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString*)domain? ?
如果允許無效的證書,同時(shí)希望驗(yàn)證證書的域名荆忍,則需要用SSL Pinning的方式驗(yàn)證格带,即驗(yàn)證證書的方式不能是AFSSLPinningModeNone,或者SSL Pinng需要本地導(dǎo)入證書刹枉,即pinnedCertificates數(shù)組不能為空叽唱。然后判斷域名是否需要驗(yàn)證域名,如果需要微宝,則將域名加入需要驗(yàn)證的對(duì)象中
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) { //需要驗(yàn)證域名
? ? [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)]; //將域名加入驗(yàn)證對(duì)象中
? ? } else {
? ? ? ? [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
? ? }
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) { //默認(rèn)驗(yàn)證方式
? ? return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust); //加驗(yàn)證書
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
? ? return NO;
}
然后判斷驗(yàn)證方式如果是AFSSLPinningModeNone且不允許無效證書尔觉,則調(diào)用AFServerTrustIsValid方法進(jìn)行校驗(yàn)
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
? ? BOOL isValid = NO;
? ? SecTrustResultType result;
? ? __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out); //方法驗(yàn)證
? ? isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out: //goto語句直接
? ? return isValid;
}
通過系統(tǒng)方法SecTrustEvaluate校驗(yàn)證書,將校驗(yàn)結(jié)果存儲(chǔ)在result中芥吟,同時(shí)通過__Require_noErr_Quiet宏來處理該方法返回error的情況:
如果該方法調(diào)用過程中失敗侦铜,即errorCode不為0,則通過goto語句跳轉(zhuǎn)钟鸵,isValid直接返回NO钉稍。如果該方法調(diào)用成功,則根據(jù)result來判斷isValid是否為YES棺耍。當(dāng)值為kSecTrustResultUnspecified或者kSecTrustResultProceed時(shí)贡未,驗(yàn)證通過。接下來處理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è)置為錨點(diǎn)證書
? ? if (!AFServerTrustIsValid(serverTrust)) { //校驗(yàn)證書
? ? ? ? return NO;
? ? }
? ? NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
? ? for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) { //本地證書數(shù)組中是否包含和服務(wù)端下發(fā)的證書內(nèi)容一樣的證書
? ? ? ? if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
? ? ? ? ? ? return YES; //如果包含蒙袍,則校驗(yàn)通過
? ? ? ? }
? ? }
? ? return NO; //否則不通過
}
因?yàn)閷?dǎo)入APP Bundle中的證書不是CA頒發(fā)的俊卤,不受信任,所以調(diào)用SecTrustSetAnchorCertificates方法將先將這些證書設(shè)置為serverTrust證書鏈上的錨點(diǎn)證書害幅,類似于將這些證書設(shè)置為系統(tǒng)信任的根證書消恍,然后調(diào)用AFServerTrustIsValid方法校驗(yàn)serverTrust證書鏈時(shí),如果遇到錨點(diǎn)證書以现,則終止驗(yàn)證狠怨。然后調(diào)用AFCertificateTrustChainForServerTrust方法獲取serverTrust的證書鏈serverCertificates约啊,遍歷證書鏈直到發(fā)現(xiàn)本地證書pinnedCertificates中有內(nèi)容相同的證書,服務(wù)端下發(fā)的證書在本地認(rèn)可的證書范圍內(nèi)佣赖,校驗(yàn)成功恰矩,如果沒有則校驗(yàn)失敗。
接下來處理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; //匹配成功外傅,校驗(yàn)成功
}
該方法首先獲取serverTrust證書鏈的公鑰,然后匹配本地的證書公鑰和serverTrust的公鑰俩檬,本地的公鑰通過self.pinnedPublicKeys屬性維護(hù)萎胰,在之前設(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;
? ? }
}
如果匹配成功,則返回校驗(yàn)成功豆胸,否則失敗奥洼。匹配方法AFSecKeyIsEqualToKey調(diào)用isEqual:方法進(jìn)行判斷巷疼。
總結(jié)
AFN框架的AFSecurityPolicy類為我們實(shí)現(xiàn)了HTTPS證書校驗(yàn)的功能晚胡,且同時(shí)支持三種方式校驗(yàn)證書,開發(fā)者可以根據(jù)不同情況進(jìn)行選擇嚼沿,如果是CA頒發(fā)的證書估盘,開發(fā)者不用做額外邏輯,使用起來十分方便骡尽。
參考大神作品 :https://blog.csdn.net/panfeng200866/article/details/69662266