iOS_學(xué)習(xí)AFNetworking(02)--AFSecurityPolicy(網(wǎng)絡(luò)通信安全策略模塊)

前言: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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末遣妥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子攀细,更是在濱河造成了極大的恐慌箫踩,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谭贪,死亡現(xiàn)場(chǎng)離奇詭異境钟,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)俭识,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門慨削,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人套媚,你說我怎么就攤上這事缚态。” “怎么了堤瘤?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵玫芦,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我本辐,道長(zhǎng)姨俩,這世上最難降的妖魔是什么蘸拔? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮环葵,結(jié)果婚禮上调窍,老公的妹妹穿的比我還像新娘。我一直安慰自己张遭,他們只是感情好邓萨,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著菊卷,像睡著了一般缔恳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上洁闰,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天歉甚,我揣著相機(jī)與錄音,去河邊找鬼扑眉。 笑死纸泄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的腰素。 我是一名探鬼主播聘裁,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼弓千!你這毒婦竟也來了衡便?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤洋访,失蹤者是張志新(化名)和其女友劉穎镣陕,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姻政,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡呆抑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扶歪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片理肺。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖善镰,靈堂內(nèi)的尸體忽然破棺而出妹萨,到底是詐尸還是另有隱情,我是刑警寧澤炫欺,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布乎完,位于F島的核電站,受9級(jí)特大地震影響品洛,放射性物質(zhì)發(fā)生泄漏树姨。R本人自食惡果不足惜摩桶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望帽揪。 院中可真熱鬧硝清,春花似錦、人聲如沸转晰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽查邢。三九已至蔗崎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扰藕,已是汗流浹背缓苛。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留邓深,地道東北人未桥。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像庐完,于是被迫代替她去往敵國(guó)和親钢属。 傳聞我的和親對(duì)象是個(gè)殘疾皇子徘熔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容