原文鏈接:http://my.oschina.net/kgdugyiy/blog/600367
1. HTTPS/SSL的基本原理
安全套接字層 (Secure Socket Layer, SSL) 是用來實(shí)現(xiàn)互聯(lián)網(wǎng)安全通信的最普遍的標(biāo)準(zhǔn)握童。Web 應(yīng)用程序使用 HTTPS(基于 SSL 的 HTTP)抹竹,HTTPS 使用數(shù)字證書來確保在服務(wù)器和客戶端之間進(jìn)行安全都办、加密的通信乍丈。在 SSL 連接中唁盏,客戶機(jī)和服務(wù)器在發(fā)送數(shù)據(jù)之前都要對(duì)數(shù)據(jù)進(jìn)行加密绿饵,然后由接受方對(duì)其進(jìn)行解密捷凄。
當(dāng)瀏覽器(客戶端)需要與某個(gè)安全站點(diǎn)建立連接時(shí)娃闲,先建立TCP連接(三次握手)虚汛,然后再發(fā)生 SSL會(huì)話握手:
- 瀏覽器將通過網(wǎng)絡(luò)發(fā)送請(qǐng)求安全會(huì)話的消息(通常請(qǐng)求以 https 而非 http 開頭的 URL)。
- 服務(wù)器通過發(fā)送其證書(包括公鑰)進(jìn)行響應(yīng)皇帮。
- 瀏覽器將檢驗(yàn)服務(wù)器的證書是否有效卷哩,并檢驗(yàn)該證書是否是由其證書位于瀏覽器的數(shù)據(jù)庫中的(并且是可信的)CA 所簽發(fā)的。它還將檢驗(yàn) CA 證書是否已過期属拾。
- 如果證書有效将谊,瀏覽器將生成一個(gè)--一次性的、唯一的--會(huì)話密鑰渐白,并使用服務(wù)器的公鑰對(duì)該會(huì)話密鑰進(jìn)行加密尊浓。然后,瀏覽器將把加密的會(huì)話密鑰發(fā)送給服務(wù)器礼预,這樣服務(wù)器和瀏覽器都有一份會(huì)話密鑰眠砾。
- 服務(wù)器可以使用其專用密鑰對(duì)消息進(jìn)行解密,然后恢復(fù)會(huì)話密鑰托酸。
握手之后褒颈,即表示客戶端已驗(yàn)證了 Web 站點(diǎn)的身份,并且只有該客戶端和 Web 服務(wù)器擁有會(huì)話密鑰副本励堡。從現(xiàn)在開始谷丸,客戶機(jī)和服務(wù)器便可以使用該會(huì)話密鑰對(duì)彼此間的所有通信進(jìn)行加密。這樣就確保了客戶機(jī)和服務(wù)器之間的通信的安全性应结。
上面是一般也是應(yīng)用最普遍的單向驗(yàn)證方式刨疼,由瀏覽器(客戶端)來驗(yàn)證服務(wù)端的合法性;其實(shí)也可以做雙向驗(yàn)證鹅龄,服務(wù)器也可以驗(yàn)證瀏覽器(客戶端)的合法性揩慕,不過一般使用在銀行業(yè)務(wù)上,比如U盾之類扮休。我們現(xiàn)在關(guān)注普遍的單向驗(yàn)證方式的應(yīng)用迎卤。
2. iOS移動(dòng)開發(fā)HTTPS應(yīng)用現(xiàn)狀
當(dāng)下絕大部份的移動(dòng)互聯(lián)網(wǎng)項(xiàng)目都采用HTTP、HTTPS協(xié)議作為前后端的數(shù)據(jù)接口協(xié)議玷坠。而iOS開發(fā)群體中蜗搔,絕大部分都在項(xiàng)目中應(yīng)用了第三方開源的HTTP請(qǐng)求框架AFNetworking來快速而高效的開發(fā)劲藐。AFNetworking請(qǐng)求HTTP接口簡直是簡單得不能再簡單了。只不過從iOS9.0開始需要設(shè)置Info.plist中App Transport Security打開非HTTP的資源加載樟凄,因?yàn)锳pple默認(rèn)只允許采用經(jīng)過權(quán)威證書頒發(fā)機(jī)構(gòu)簽名的證書的HTTPS站點(diǎn)的訪問聘芜,一切是為了安全。安全缝龄。安全汰现。
那么我們重點(diǎn)來分析采用HTTPS協(xié)議的后臺(tái)接口的一般使用方式: HTTPS的服務(wù)器配置的證書分兩大類,一類是經(jīng)過權(quán)威機(jī)構(gòu)簽名頒發(fā)的證書二拐,這樣證書通常是要花錢買服務(wù)的,而且不便宜,當(dāng)然現(xiàn)在也有少數(shù)機(jī)構(gòu)提供免費(fèi)的證書簽名服務(wù)服鹅。另一類就是服務(wù)器配置的是研發(fā)人員自己簽名生成的證書凳兵。
3.AFN調(diào)用使用權(quán)威機(jī)構(gòu)頒發(fā)證書的HTTPS接口
現(xiàn)在AFNetworking框架已經(jīng)修復(fù)了之前爆出的SSL中間人攻擊漏洞百新,并強(qiáng)烈要求開發(fā)者使用公鑰綁定或者證書綁定的安全策略,那么正確使用AFNetworking請(qǐng)求這類證書的HTTPS站點(diǎn)代碼很簡單如下:
AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey]; policy.validatesDomainName = YES; AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; manager.securityPolicy = policy; manager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
對(duì)于這類證書的站點(diǎn)庐扫,Info.plist都不需要設(shè)置饭望,因?yàn)橐呀?jīng)是權(quán)威機(jī)構(gòu)頒發(fā)的證書了,我們只需要設(shè)置驗(yàn)證綁定方式和驗(yàn)證域名以防止中間人攻擊形庭,畢竟申請(qǐng)證書是花了錢(現(xiàn)在也有免費(fèi)的申請(qǐng)铅辞,比如WoSign),省事一點(diǎn)萨醒。
4.AFN調(diào)用使用我們自己簽名證書的HTTPS接口
對(duì)于使用我們自己簽名的證書來說斟珊,瀏覽器打開web站點(diǎn)也會(huì)默認(rèn)阻止訪問,除非用戶手動(dòng)把該站點(diǎn)加入信任列表富纸,這個(gè)手動(dòng)加入的過程其實(shí)就是不去驗(yàn)證服務(wù)器的合法性囤踩,任性的認(rèn)為服務(wù)器是可信賴的。 那么手動(dòng)加入信任列表晓褪,這樣會(huì)導(dǎo)致證書的驗(yàn)證過程壓根沒發(fā)生堵漱,雖然可以成功訪問目標(biāo)服務(wù)器返回我們需要的數(shù)據(jù),其實(shí)涣仿,這中間很有可能返回的數(shù)據(jù)不是正真的目標(biāo)服務(wù)器返回的數(shù)據(jù)勤庐,也可能是網(wǎng)絡(luò)傳輸中間的第三者偽裝返回的數(shù)據(jù)。傳輸?shù)臄?shù)據(jù)被人竊取甚至纂改都是很可能的好港。
4.1 不正確的做法
瀏覽器手動(dòng)加入自簽名站點(diǎn)到信任列表這個(gè)操作的功能相當(dāng)于iOS開發(fā)中AFNetworking的API的如下做法:
A 非權(quán)威機(jī)構(gòu)頒發(fā)證書的HTTPS請(qǐng)求一樣必須先在Info.plist設(shè)置如下:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
B AFNetworking代碼設(shè)置SecurityPolicy
站點(diǎn) https://tv.diveinedu.com 是我前面博客所講的配置方法配置的自簽名證書愉镰。
此處是博客鏈接
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; //允許非權(quán)威機(jī)構(gòu)頒發(fā)的證書 manager.securityPolicy.allowInvalidCertificates = YES; //也不驗(yàn)證域名一致性 manager.securityPolicy.validatesDomainName = NO; //關(guān)閉緩存避免干擾測(cè)試 manager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData; [manager GET:@"https://tv.diveinedu.com/channel/" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { NSLog(@"%@",responseObject); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { NSLog(@"%@",error); }];
經(jīng)過如上兩步設(shè)置之后,我們可以在iOS應(yīng)用中訪問我們采用自簽名證書的HTTPS站點(diǎn)了钧汹。但是這個(gè)是不安全的丈探,因?yàn)樗跊]有使用HTTPS/SSL代理和使用像Charles那樣的HTTPS/SSL代理的情況下都可以訪問服務(wù)器資源. 完全可以說是白費(fèi)功夫,只能防止“君子”在網(wǎng)絡(luò)中用Wireshark之類來TCP抓包嗅探崭孤。因?yàn)楫吘惯€是HTTPS加密了傳輸數(shù)據(jù)了类嗤。那為什么我要說這樣是白費(fèi)功夫呢糊肠,因?yàn)檫@個(gè)辦法不能防止中間人攻擊!比如用戶可以給手機(jī)設(shè)置HTTPS的SSL代理(比如Charles)遗锣,完全可以在代理中看到明文數(shù)據(jù)货裹,所以,既然用了HTTPS就要防止中間人攻擊精偿,不然還不如不用HTTPS弧圆。
下面我們來看看怎么用Charles代理抓包工具所抓到的HTTPS傳輸?shù)臄?shù)據(jù):
上圖是在Mac上運(yùn)行Charles工具代理抓包,真機(jī)和Mac電腦同一個(gè)局域網(wǎng)笔咽,并設(shè)置代理為Mac機(jī)的IP和Charles的代理端口8888搔预,然后啟動(dòng)App請(qǐng)求網(wǎng)絡(luò)后抓到的數(shù)據(jù)。是不是很意外啊叶组。HTTPS的數(shù)據(jù)也抓出明文了拯田。 顯然這樣是非常不安全的,那么當(dāng)我們使用自簽名證書的時(shí)候甩十,我們?cè)撊绾蝸碓贏pp端(客戶端)嚴(yán)格的驗(yàn)證服務(wù)器的合法性呢船庇?
4.2 正確的做法
我們要在App端嚴(yán)格驗(yàn)證服務(wù)器的合法性,防止網(wǎng)絡(luò)中間的代理或者防火墻進(jìn)行中間人的攻擊和證書欺騙,那么我們需要把服務(wù)器配置的證書打包到客戶端程序中(私鑰留服務(wù)器不要分發(fā)不用泄露,非常重要),在代碼里去讀取該證書/公鑰信息和服務(wù)器返回的進(jìn)行匹配驗(yàn)證. 在iOS開發(fā)中,從Xcode7和iOS9開始,Apple提升了App的網(wǎng)絡(luò)安全性,App默認(rèn)只能進(jìn)行對(duì)采用權(quán)威機(jī)構(gòu)簽名頒發(fā)證書的Web站點(diǎn)進(jìn)行訪問(信任的HTTPS),而自簽名的證書的HTTPS站點(diǎn)也被列為屬于例外,所以我們需要在App的 Info.plist 中單獨(dú)為我們的域名設(shè)置 Exception Domains "白名單",而不是打開 Allow Arbitrary Loads 全部放開,設(shè)置信息如下:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>tv.diveinedu.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
這樣就不像上面那個(gè)方法那樣一刀切全部放開, 而是單獨(dú)為某個(gè)域名放開設(shè)置.當(dāng)然上面也可以使用放開全部的設(shè)置NSAllowsArbitraryLoads為true.但是我建議使用白名單.
除此之外,要做到嚴(yán)格驗(yàn)證防止像Charles那樣的中間人代理抓包,AFNetworking代碼應(yīng)該用如下設(shè)置:
//服務(wù)器端配置的包含公鑰的證書分發(fā)到客戶端后,需要轉(zhuǎn)換為DER格式的證書文件. //openssl x509 -outform der -in tv.diveinedu.com.crt -out tv.diveinedu.com.der NSString *certFilePath = [[NSBundle mainBundle] pathForResource:@"tv.diveinedu.com" ofType:@"der"]; NSData *certData = [NSData dataWithContentsOfFile:certFilePath]; NSSet *certSet = [NSSet setWithObject:certData]; AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey withPinnedCertificates:certSet]; policy.allowInvalidCertificates = YES; AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; manager.securityPolicy = policy; //關(guān)閉緩存避免干擾測(cè)試 manager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData; [manager GET:@"https://tv.diveinedu.com/channel/" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { NSLog(@"%@",responseObject); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { NSLog(@"%@",error); }];
上面的代碼能夠驗(yàn)證服務(wù)器身份在沒有使用代理的時(shí)候可以正常訪問服務(wù)器的資源,但是一旦用戶給手機(jī)網(wǎng)絡(luò)設(shè)置使用了如Charle那樣的HTTPS/SSL代理服務(wù),則會(huì)出現(xiàn)服務(wù)器證書驗(yàn)證失敗,SSL網(wǎng)絡(luò)連接會(huì)斷開,老板再也不用擔(dān)心數(shù)據(jù)接口被人抓包或者代理給扒出來了.故達(dá)到防止中間人攻擊的效果.
當(dāng)使用Charles SSL代理時(shí)Xcode調(diào)試終端出錯(cuò)信息圖:
代理服務(wù)器Charles那邊的出錯(cuò)信息圖:
最后,關(guān)于iOS9和OSX 10.11 開發(fā)時(shí),Xcode的Info.plist的NSAppTransportSecurity詳細(xì)設(shè)置方法請(qǐng)參考Apple官方文檔: NSAppTransportSecurity Reference