上一篇《iOS安全系列之一:HTTPS》被CocoaChina轉(zhuǎn)載,還順便上了下頭條: 打造安全的App!iOS安全系列之 HTTPS,但那篇文章只是介紹了比較偏應(yīng)用的初級(jí)知識(shí),對(duì)于想要深入了解HTTPS的同學(xué)來(lái)說(shuō)是遠(yuǎn)遠(yuǎn)不夠的,剛好本人最近工作上也遇到并解決了一些HTTPS相關(guān)的問(wèn)題,以此為契機(jī),決定寫這篇更深入介紹HTTPS的文章。
本文分為以下五節(jié):
- 中間人攻擊:介紹中間人攻擊常見(jiàn)方法,并模擬了一個(gè)簡(jiǎn)單的中間人攻擊;
- 校驗(yàn)證書(shū)的正確姿勢(shì):介紹校驗(yàn)證書(shū)的一些誤區(qū),并討論了正確校驗(yàn)方式;
-
ATS:討論下 iOS 9.0 新發(fā)布的的特性
App Transport Security; - 調(diào)試SSL/TLS:討論使用Wireshark進(jìn)行SSL/TLS調(diào)試的方法;
- 后記
其中第1節(jié)“中間人”是比較常見(jiàn)基礎(chǔ)的知識(shí),網(wǎng)上也可以找到相關(guān)的資料,如果對(duì)中間人攻擊已經(jīng)有了足夠的了解,可以跳過(guò)。后面幾節(jié)則是個(gè)人在iOS方面的實(shí)踐總結(jié),除了一些與系統(tǒng)相關(guān)的特性外,大部分都是系統(tǒng)無(wú)關(guān)的通用知識(shí),并且每一章節(jié)都比較獨(dú)立,所以可以直接跳到感興趣的地方閱讀。
1. 中間人攻擊
關(guān)于HTTPS,我經(jīng)常會(huì)提到的就是中間人攻擊,那究竟什么是中間人攻擊呢?中間人攻擊,即所謂的Man-in-the-middle attack(MITM),顧名思義,就是攻擊者插入到原本直接通信的雙方,讓雙方以為還在直接跟對(duì)方通訊,但實(shí)際上雙方的通信對(duì)方已變成了中間人,信息已經(jīng)是被中間人獲取或篡改。

當(dāng)然,本文并不是科普性文章,本節(jié)就針對(duì)HTTPS攻擊,特別是HTTPS在App這一應(yīng)用場(chǎng)景下的常見(jiàn)的攻擊手段進(jìn)行分析討論。
由前文我們知道,HTTPS在建立了TCP連接之后,會(huì)進(jìn)行SSL握手(SSL Handshake)來(lái)校驗(yàn)證書(shū),協(xié)商加密協(xié)議和對(duì)稱加密的密鑰,之后就會(huì)使用協(xié)商好的密鑰來(lái)進(jìn)行傳輸。所以HTTPS攻擊一般分為SSL連接建立前的攻擊,以及HTTPS傳輸過(guò)程中的攻擊;
常見(jiàn)的HTTPS中間人攻擊,首先需要結(jié)合ARP、DNS欺騙等技術(shù),來(lái)對(duì)會(huì)話進(jìn)行攔截,
1.1 SSL證書(shū)欺騙攻擊
此類攻擊較為簡(jiǎn)單常見(jiàn)。首先通過(guò)ARP欺騙、DNS劫持甚至網(wǎng)關(guān)劫持等等,將客戶端的訪問(wèn)重定向到攻擊者的機(jī)器,讓客戶端機(jī)器與攻擊者機(jī)器建立HTTPS連接(使用偽造證書(shū)),而攻擊者機(jī)器再跟服務(wù)端連接。這樣用戶在客戶端看到的是相同域名的網(wǎng)站,但瀏覽器會(huì)提示證書(shū)不可信,用戶不點(diǎn)擊繼續(xù)瀏覽就能避免被劫持的。所以這是最簡(jiǎn)單的攻擊方式,也是最容易識(shí)別的攻擊方式。
此類攻擊有個(gè)經(jīng)典的工具:SSLSniff。SSLSniff是大神Moxie Marlinspike開(kāi)發(fā)的工具,該工具一開(kāi)始是設(shè)計(jì)用于上一篇文章中提到的Basic Constaints 漏洞的,這類系統(tǒng)級(jí)別的漏洞,基本上可以讓你不知不覺(jué);現(xiàn)在的操作系統(tǒng)和瀏覽器基本修復(fù)了這一漏洞。但也可以使用SSLSniff來(lái)偽造證書(shū)實(shí)現(xiàn)釣魚(yú)攻擊。

防范措施:
釣魚(yú)類攻擊,App直接調(diào)用系統(tǒng)API創(chuàng)建的HTTPS連接(NSURLConnection)一般不會(huì)受到影響,只使用默認(rèn)的系統(tǒng)校驗(yàn),只要系統(tǒng)之前沒(méi)有信任相關(guān)的偽造證書(shū),校驗(yàn)就直接失敗,不會(huì)SSL握手成功;但如果是使用WebView瀏覽網(wǎng)頁(yè),需要在UIWebView中加入較強(qiáng)的授權(quán)校驗(yàn),禁止用戶在校驗(yàn)失敗的情況下繼續(xù)訪問(wèn)。
1.2 SSL剝離攻擊(SSLStrip)
SSL剝離,即將HTTPS連接降級(jí)到HTTP連接。假如客戶端直接訪問(wèn)HTTPS的URL,攻擊者是沒(méi)辦法直接進(jìn)行降級(jí)的,因?yàn)镠TTPS與HTTP雖然都是TCP連接,但HTTPS在傳輸HTTP數(shù)據(jù)之前,需要在進(jìn)行了SSL握手,并協(xié)商傳輸密鑰用來(lái)后續(xù)的加密傳輸;假如客戶端與攻擊者進(jìn)行SSL握手,而攻擊者無(wú)法提供可信任的證書(shū)來(lái)讓客戶端驗(yàn)證通過(guò)進(jìn)行連接,所以客戶端的系統(tǒng)會(huì)判斷為SSL握手失敗,斷開(kāi)連接。
該攻擊方式主要是利用用戶并不會(huì)每次都直接在瀏覽器上輸入https://xxx.xxx.com來(lái)訪問(wèn)網(wǎng)站,或者有些網(wǎng)站并非全網(wǎng)HTTPS,而是只在需要進(jìn)行敏感數(shù)據(jù)傳輸時(shí)才使用HTTPS的漏洞。中間人攻擊者在劫持了客戶端與服務(wù)端的HTTP會(huì)話后,將HTTP頁(yè)面里面所有的https://超鏈接都換成http://,用戶在點(diǎn)擊相應(yīng)的鏈接時(shí),是使用HTTP協(xié)議來(lái)進(jìn)行訪問(wèn);這樣,就算服務(wù)器對(duì)相應(yīng)的URL只支持HTTPS鏈接,但中間人一樣可以和服務(wù)建立HTTPS連接之后,將數(shù)據(jù)使用HTTP協(xié)議轉(zhuǎn)發(fā)給客戶端,實(shí)現(xiàn)會(huì)話劫持。
這種攻擊手段更讓人難以提防,因?yàn)樗褂肏TTP,不會(huì)讓瀏覽器出現(xiàn)HTTPS證書(shū)不可信的警告,而且用戶很少會(huì)去看瀏覽器上的URL是https://還是http://。特別是App的WebView中,應(yīng)用一般會(huì)把URL隱藏掉,用戶根本無(wú)法直接查看到URL出現(xiàn)異常。

防范措施:
該種攻擊方式同樣無(wú)法劫持App內(nèi)的HTTPS連接會(huì)話,因?yàn)锳pp中傳入請(qǐng)求的URL參數(shù)是固定帶有https://的;但在WebView中打開(kāi)網(wǎng)頁(yè)同樣需要注意,在非全網(wǎng)HTTPS的網(wǎng)站,建議對(duì)WebView中打開(kāi)的URL做檢查,檢查應(yīng)該使用https://的URL是否被篡改為http://;也建議服務(wù)端在配置HTTPS服務(wù)時(shí),加上“HTTP Strict Transport Security”配置項(xiàng)。
1.3 針對(duì)SSL算法進(jìn)行攻擊
上述兩種方式,技術(shù)含量較低,而且一般只能影響 WebApp,而很難攻擊到 Native App , 所以高階的 Hacker,會(huì)直接針對(duì)SSL算法相關(guān)漏洞進(jìn)行攻擊,期間會(huì)使用很多的密碼學(xué)相關(guān)手段。由于本人非專業(yè)安全相關(guān)人員,沒(méi)有多少相關(guān)實(shí)踐經(jīng)驗(yàn),所以本節(jié)不會(huì)深入講解相關(guān)的攻擊原理和手段,有興趣的同學(xué)可以查看以下拓展閱讀:
防范措施:
這類攻擊手段是利用SSL算法的相關(guān)漏洞,所以最好的防范措施就是對(duì)服務(wù)端 SSL/TLS 的配置進(jìn)行升級(jí):
- 只支持盡量高版本的TLS(最低TLSv1);
- 禁用一些已爆出安全隱患的加密方法;
- 使用2048位的數(shù)字證書(shū);
1.4 模擬最簡(jiǎn)單的攻擊
經(jīng)過(guò)上述幾種攻擊方式的說(shuō)明之后,我們來(lái)模擬下最簡(jiǎn)單的中間人攻擊。
中間人攻擊步驟方式的上文已經(jīng)說(shuō)過(guò)了,流量劫持相關(guān)操作不是本文重點(diǎn),可以參考流量劫持是如何產(chǎn)生的?, 本例直接使用Charles來(lái)做代理,對(duì)流量進(jìn)行劫持。并使用SSL代理來(lái)模擬下對(duì)iPhone設(shè)備HTTPS請(qǐng)求的中間人攻擊,讓大家在思考理解中間人攻擊方式的同時(shí),了解在開(kāi)發(fā)中如何防范類似的攻擊。
1) Charles設(shè)置代理
在Charles中開(kāi)啟并設(shè)置HTTP代理和SSL代理,Menu -> Proxy -> Proxy Setting,設(shè)置如圖:
HTTP代理設(shè)置,注意記住端口號(hào)為:8888

SSL代理設(shè)置,在Locations上可以設(shè)置想要進(jìn)行SSL代理的域名,這里以百度的百付寶*.baifubao.com為模擬對(duì)象。

2) 在iPhone端設(shè)置HTTP代理
在Mac上獲取當(dāng)前機(jī)器的IP地址:
ifconfig en0:

還有一個(gè)簡(jiǎn)單的方法,按住option+點(diǎn)擊頂部菜單欄的WiFi網(wǎng)絡(luò)圖標(biāo):

可以看到當(dāng)前電腦的IP地址為:192.168.199.249。
將iPhone連接到與電腦相同的WiFi,在iPhone設(shè)置中:無(wú)線局域網(wǎng) -> 已連接WiFi右邊的Info詳情圖標(biāo) -> HTTP代理 -> 手動(dòng) -> 設(shè)置HTTP代理:

設(shè)置完成之后,打開(kāi)Safari隨便訪問(wèn)一個(gè)網(wǎng)頁(yè),初次設(shè)置代理的話,Charles會(huì)彈出一個(gè)iPhone請(qǐng)求代理的確認(rèn)框,點(diǎn)擊Allow即可。然后在Charles上就可以看到iPhone上的HTTP請(qǐng)求了。為了避免Mac上的請(qǐng)求過(guò)多影響對(duì)被代理iPhone上HTTP請(qǐng)求的查看和調(diào)試,可以在Charles取消Mac的代理:Menu -> Proxy -> 取消勾選Mac OS X Proxy 即可。
假如你訪問(wèn)的是被代理的目標(biāo) URL http://www.baifubao.com 則打不開(kāi)網(wǎng)頁(yè)。因?yàn)閕Phone的HTTPS請(qǐng)求已經(jīng)被Charles攔截,但iPhone無(wú)法信任Charles的證書(shū),所以SSL Handshake失敗,無(wú)法建立HTTPS連接。

3) 偽造證書(shū)欺騙
在被代理的iPhone上打開(kāi)Safari,訪問(wèn)http://www.charlesproxy.com/getssl,會(huì)彈出安裝描述符文件的界面,該描述文件包含了Charles根證書(shū):

注意:這個(gè)Charles證書(shū)是內(nèi)置在Charles中的,可以在菜單Help -> SSL Proxying可以直接保存和安裝證書(shū)。安裝后的描述文件可以在iPhone設(shè)備的設(shè)置 -> 通用 -> 描述文件進(jìn)行查看和管理。
“安裝”完成之后,就會(huì)將Charles根證書(shū)加入系統(tǒng)可信任證書(shū)列表中,使用該證書(shū)簽發(fā)的子證書(shū)也會(huì)被系統(tǒng)信任。Charles會(huì)為之前SSL代理設(shè)置中配置的域名生成對(duì)應(yīng)的SSL證書(shū),這樣偽造證書(shū)的證書(shū)就實(shí)現(xiàn)了欺騙??梢允褂肕ac SSL代理查看下:

4) 結(jié)果驗(yàn)證
下載百度App,然后登錄賬號(hào),在我 -> 我的錢包,就會(huì)訪問(wèn)百付寶:

看到已成功獲取到HTTPS請(qǐng)求包的內(nèi)容。從這里,我們可以猜測(cè)出該App是使用系統(tǒng)默認(rèn)的校驗(yàn)方式:系統(tǒng)信任了這個(gè)中間人服務(wù)器返回的SSL證書(shū),App就信任了這一校驗(yàn),SSL握手成功;而沒(méi)有對(duì)服務(wù)器證書(shū)進(jìn)行本地對(duì)比校驗(yàn)。這是當(dāng)下非常多App存在的安全隱患。
這個(gè)簡(jiǎn)單的SSL代理模擬了簡(jiǎn)單釣魚(yú)式的中間人攻擊,大家應(yīng)該都基本明白了這種攻擊方式的所針對(duì)的漏洞,以及防范這種攻擊方法的措施:
- 不要隨意連入公共場(chǎng)合內(nèi)的WiFi,或者使用未知代理服務(wù)器
- 不要安裝不可信或突然出現(xiàn)的描述文件,信任偽造的證書(shū);
- App內(nèi)部需對(duì)服務(wù)器證書(shū)進(jìn)行單獨(dú)的對(duì)比校驗(yàn),確認(rèn)證書(shū)不是偽造的;
2. 校驗(yàn)證書(shū)的正確姿勢(shì)
上一節(jié)對(duì)中間人攻擊進(jìn)行了簡(jiǎn)單介紹,本節(jié)就上一節(jié)我們遇到的安全隱患問(wèn)題,來(lái)討論下在App中,應(yīng)該怎么校驗(yàn)服務(wù)器返回的SSL證書(shū),來(lái)保證HTTPS通信的安全。上一篇文章《iOS安全系列之一:HTTPS》有對(duì)基本校驗(yàn)過(guò)程相關(guān)代碼進(jìn)行講解,本文不會(huì)贅述這些細(xì)節(jié),而是主要討論校驗(yàn)證書(shū)中幾個(gè)重要的點(diǎn):
2.1 域名驗(yàn)證
前不久,iOS上最知名的網(wǎng)絡(luò)開(kāi)源庫(kù)AFNetworking爆出HTTPS校驗(yàn)漏洞,該漏洞是因?yàn)槠湫r?yàn)策略模塊 AFSecurityPolicy 內(nèi)的參數(shù) validatesDomainName 默認(rèn)為NO,這會(huì)導(dǎo)致校驗(yàn)證書(shū)的時(shí)候不會(huì)校驗(yàn)這個(gè)證書(shū)對(duì)應(yīng)的域名。即請(qǐng)求返回的服務(wù)器證書(shū),只要是可信任CA機(jī)構(gòu)簽發(fā)的,都會(huì)校驗(yàn)通過(guò),這是非常嚴(yán)重的漏洞。該漏洞已在v2.5.2版本中修復(fù),對(duì)應(yīng)Git版本號(hào)3e631b203dd95bb82dfbcc2c47a2d84b59d1eeb4。
這個(gè)漏洞以及AFNetworking的相關(guān)源碼會(huì)讓很多人以為系統(tǒng)的默認(rèn)校驗(yàn)是不校驗(yàn)證書(shū)對(duì)應(yīng)域名的,實(shí)際上并非如此。這里AFNetworking確有畫蛇添足之嫌。首先我們查看下系統(tǒng)的默認(rèn)校驗(yàn)策略:
<figure class="highlight" style="box-sizing: border-box; color: rgb(0, 0, 0); font-family: "PingFang SC", "Source Han Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 1px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px;">
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
//1)獲取trust object
SecTrustRef trust = challenge.protectionSpace.serverTrust;
//獲取默認(rèn)的校驗(yàn)策略
CFArrayRef defaultPolicies = NULL;
SecTrustCopyPolicies(serverTrust, &defaultPolicies);
NSLog(@"Default Trust Policies: %@", (__bridge id)defaultPolicies);
//...
}
</figure>
打印默認(rèn)校驗(yàn)策略信息:
5 : <CFString 0x197814dc0 [0x196ea5fa0]>{contents = "ValidRoot"} = <CFBoolean 0x196ea6340 [0x196ea5fa0]>{value = true}
6 : <CFString 0x197814b20 [0x196ea5fa0]>{contents = "SSLHostname"} = <CFString 0x170226b60 [0x196ea5fa0]>{contents = "xxx.xxx.com"}
8 : <CFString 0x197814da0 [0x196ea5fa0]>{contents = "ValidLeaf"} = <CFBoolean 0x196ea6340 [0x196ea5fa0]>{value = true}
從打印信息來(lái)看,系統(tǒng)的默認(rèn)校驗(yàn)策略中已包含了域名校驗(yàn)。然后再看AFSecurityPolicy中相關(guān)源碼:
<figure class="highlight" style="box-sizing: border-box; color: rgb(0, 0, 0); font-family: "PingFang SC", "Source Han Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 1px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px;">
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
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);
//...
}
</figure>
這其實(shí)也是很多開(kāi)發(fā)者在處理異常與默認(rèn)邏輯分支時(shí)會(huì)犯的錯(cuò)誤,這段邏輯推薦實(shí)現(xiàn)方式是:
<figure class="highlight" style="box-sizing: border-box; color: rgb(0, 0, 0); font-family: "PingFang SC", "Source Han Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 1px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px;">
//取代validatesDomainName,默認(rèn)為NO,就是系統(tǒng)默認(rèn)行為
@property (nonatomic, assign) BOOL skipDomainNameValidation;
//校驗(yàn)
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
if (self.skipDomainNameValidation) {
NSMutableArray *policies = [NSMutableArray array];
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
}
//...
}
</figure>
從代碼上看,邏輯是否變得更清晰了?而且也表明系統(tǒng)默認(rèn)的校驗(yàn)方式是會(huì)驗(yàn)證域名的。實(shí)際上調(diào)用SecTrustSetPolicies來(lái)重新設(shè)置校驗(yàn)策略,主要是用于使用IP進(jìn)行HTTPS請(qǐng)求,或者一個(gè)證書(shū)用于多個(gè)域名的場(chǎng)景;在這些場(chǎng)景下,服務(wù)器證書(shū)上的域名和請(qǐng)求域名(可能是IP,也可能是其他域名)就會(huì)出現(xiàn)不一致,導(dǎo)致校驗(yàn)不通過(guò);這就需要重新設(shè)置下校驗(yàn)策略,把這個(gè)證書(shū)對(duì)應(yīng)的域名設(shè)置下。詳細(xì)說(shuō)明請(qǐng)查看官方文檔:《Overriding TLS Chain Validation Correctly》
2.2 校驗(yàn)證書(shū)鏈?
上一篇文章介紹系統(tǒng)驗(yàn)證SSL證書(shū)的方法和流程時(shí),不是已經(jīng)說(shuō)明了會(huì)對(duì)證書(shū)鏈進(jìn)行層層校驗(yàn),以保證證書(shū)的可信么?為什么還需要討論這一問(wèn)題?其實(shí)本節(jié)要討論的是AFNetworking中validatesCertificateChain的問(wèn)題。
先說(shuō)明下結(jié)果:在AFNetworking最新發(fā)布的V2.6.0,已經(jīng)將該特性去掉了。相關(guān)的討論:SSL Pinning: What Should Be Certificate Chain Validation Expected Behavior?#2744
AFNetworking中實(shí)現(xiàn)的驗(yàn)證證書(shū)鏈,是將App本地打包好的證書(shū)與服務(wù)器返回的證書(shū)鏈進(jìn)行數(shù)據(jù)上的一一對(duì)比,只有打包到App的證書(shū)中包含了服務(wù)器返回的證書(shū)鏈上的所有證書(shū),校驗(yàn)才會(huì)通過(guò)。如google的SSL證書(shū):

開(kāi)啟validatesCertificateChain后請(qǐng)求https://google.com,需要將GeoTrust Global CA、Google Internet Authority G2和google.com的證書(shū)都導(dǎo)入App中才能驗(yàn)證通過(guò)。請(qǐng)回憶下上一篇文章關(guān)于證書(shū)鏈的可信任機(jī)制,會(huì)發(fā)現(xiàn)這是完全沒(méi)有必要的;證書(shū)鏈的驗(yàn)證,主要由三部分來(lái)保證證書(shū)的可信:葉子證書(shū)是對(duì)應(yīng)HTTPS請(qǐng)求域名的證書(shū),根證書(shū)是被系統(tǒng)信任的證書(shū),以及這個(gè)證書(shū)鏈之間都是層層簽發(fā)可信任鏈;證書(shū)之所以能成立,本質(zhì)是基于信任鏈,這樣任何一個(gè)節(jié)點(diǎn)證書(shū)加上域名校驗(yàn)(CA機(jī)構(gòu)不會(huì)為不同的對(duì)不同的用戶簽發(fā)相同域名的證書(shū)),就確定一條唯一可信證書(shū)鏈,所以不需要每個(gè)節(jié)點(diǎn)都驗(yàn)證。
2.3打包證書(shū)校驗(yàn)
那是否就不需要在App中打包證書(shū)進(jìn)行驗(yàn)證了呢?
這時(shí)需要想想為什么偽造證書(shū)是可以實(shí)現(xiàn)中間人攻擊的?答案就在于用戶讓系統(tǒng)信任了不應(yīng)該信任的證書(shū)。用戶設(shè)置系統(tǒng)信任的證書(shū),會(huì)作為錨點(diǎn)證書(shū)(Anchor Certificate)來(lái)驗(yàn)證其他證書(shū),當(dāng)返回的服務(wù)器證書(shū)是錨點(diǎn)證書(shū)或者是基于該證書(shū)簽發(fā)的證書(shū)(可以是多個(gè)層級(jí))都會(huì)被信任。這就是基于信任鏈校驗(yàn)方式的最大弱點(diǎn)。我們不能完全相信系統(tǒng)的校驗(yàn),因?yàn)橄到y(tǒng)的校驗(yàn)依賴的證書(shū)的源很可能被污染了。這就需要選取一個(gè)節(jié)點(diǎn)證書(shū),打包到App中,作為Anchor Certificate來(lái)保證證書(shū)鏈的唯一性和可信性。
所以還是需要App本地打包證書(shū),使用SecTrustSetAnchorCertificates(SecTrustRef trust, CFArrayRef anchorCertificates)來(lái)設(shè)置Anchor Certificate進(jìn)行校驗(yàn)。需要注意的是,官方文檔《Certificate, Key, and Trust Services Reference》針對(duì)傳入的 Anchor Certificates 有說(shuō)明:
IMPORTANT
Calling this function without also calling SecTrustSetAnchorCertificatesOnly disables the trusting of any anchors other than the ones specified by this function call.
也就是說(shuō),單純調(diào)用SecTrustSetAnchorCertificates方法后不調(diào)用SecTrustSetAnchorCertificatesOnly來(lái)驗(yàn)證證書(shū),則只會(huì)相信SecTrustSetAnchorCertificates傳入的證書(shū),而不會(huì)信任其他錨點(diǎn)證書(shū)。關(guān)于這一點(diǎn),SecTrustSetAnchorCertificatesOnly方法參數(shù)講解中也有說(shuō)明:
anchorCertificatesOnly:
If true, disables trusting any anchors other than the ones passed in with the SecTrustSetAnchorCertificates function. If false, the built-in anchor certificates are also trusted. If SecTrustSetAnchorCertificates is called and SecTrustSetAnchorCertificatesOnly is not called, only the anchors explicitly passed in are trusted.
只相信傳入的錨點(diǎn)證書(shū),也就只會(huì)驗(yàn)證通過(guò)由這些錨點(diǎn)證書(shū)簽發(fā)的證書(shū)。這樣就算被驗(yàn)證的證書(shū)是由系統(tǒng)其他信任的錨點(diǎn)證書(shū)簽發(fā)的,也無(wú)法驗(yàn)證通過(guò)。
最后一個(gè)問(wèn)題:選擇證書(shū)鏈的哪一節(jié)點(diǎn)作為錨點(diǎn)證書(shū)打包到App中?很多開(kāi)發(fā)者會(huì)直接選擇葉子證書(shū)。其實(shí)對(duì)于自建證書(shū)來(lái)說(shuō),選擇哪一節(jié)點(diǎn)都是可行的。而對(duì)于由CA頒發(fā)的證書(shū),則建議導(dǎo)入頒發(fā)該證書(shū)的CA機(jī)構(gòu)證書(shū)或者是更上一級(jí)CA機(jī)構(gòu)的證書(shū),甚至可以是根證書(shū)。這是因?yàn)椋?/p>
一般葉子證書(shū)的有效期都比較短,Google和Baidu官網(wǎng)證書(shū)的有效期也就幾個(gè)月;而App由于是客戶端,需要一定的向后兼容,稍疏于檢查,今天發(fā)布,過(guò)兩天證書(shū)就過(guò)期了。
越往證書(shū)鏈的末端,證書(shū)越有可能變動(dòng);比如葉子證書(shū)由特定域名(aaa.bbb.com)改為通配域名(*.bbb.com)等等。短期內(nèi)的變動(dòng),重新部署后,有可能舊版本App更新不及時(shí)而出現(xiàn)無(wú)法訪問(wèn)的問(wèn)題。
因此使用CA機(jī)構(gòu)證書(shū)是比較合適的,至于哪一級(jí)CA機(jī)構(gòu)證書(shū),并沒(méi)有完全的定論,你可以自己評(píng)估選擇。
3. ATS
在本文發(fā)表的時(shí)間(2015-09-03),大部分的iOS開(kāi)發(fā)同學(xué)應(yīng)該升級(jí)到iOS9了,在iOS9下進(jìn)行HTTP/HTTPS請(qǐng)求時(shí)會(huì)遇到如下錯(cuò)誤:
Request failed: Error Domain=NSURLErrorDomain Code=-1022 “The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.” UserInfo=0x7fbb4a158f00 {NSUnderlyingError=0x7fbb4a1141c0 “The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.”, NSErrorFailingURLStringKey=http://api.xxx.com/mobile, NSErrorFailingURLKey=http://api.xxx.com/mobile, NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.}
這是iOS9中一個(gè)重大的更新:App Transport Security,簡(jiǎn)稱ATS。ATS對(duì)使用NSURLConnection, CFURL, 或NSURLSession 等 APIs 進(jìn)行網(wǎng)絡(luò)請(qǐng)求的行為作了一系列的強(qiáng)制要求,反逼服務(wù)器配置,以提高網(wǎng)絡(luò)數(shù)據(jù)傳輸?shù)陌踩裕?/p>
These are the App Transport Security requirements:
- The server must support at least Transport Layer Security (TLS) protocol version 1.2.
- Connection ciphers are limited to those that provide forward secrecy (see the list of ciphers below.)
- Certificates must be signed using a SHA256 or better signature hash algorithm, with either a 2048 bit or greater RSA key or a 256 bit or greater Elliptic-Curve (ECC) key. Invalid certificates result in a hard failure and no connection.
ATS要求運(yùn)行在iOS9的App,需將HTTP連接升級(jí)到HTTPS,并且TLS版本不得低于v1.2;而且規(guī)定了支持的加密套件(Cipher Suite)和證書(shū)簽名的哈希算法;如果想要向前兼容的話,可以通過(guò)設(shè)置Info.plist來(lái)降低校驗(yàn)強(qiáng)度,具體可以看這篇文章:Configuring App Transport Security Exceptions in iOS 9 and OSX 10.11。
本人升級(jí)到iOS9 GM版,從App Store上下載了一些并沒(méi)有完全支持ATS的應(yīng)用,使用起來(lái)也完全沒(méi)有問(wèn)題,估計(jì)iOS系統(tǒng)對(duì)使用低于SDK9編譯的App做了兼容,這方面也是符合預(yù)期的,畢竟ATS的影響實(shí)在太大,基本上沒(méi)有任何的App能夠幸免,比如圖片下載一般使用HTTP,而不會(huì)使用HTTPS。所以建議可以暫時(shí)使用NSAllowsArbitraryLoads來(lái)取消ATS的限制,后續(xù)慢慢完善對(duì)ATS的支持。
日益復(fù)雜脆弱的網(wǎng)絡(luò)難以保證用戶的數(shù)據(jù)安全,因此Apple才在iOS9上強(qiáng)推ATS,反向逼迫服務(wù)端升級(jí),以提供更安全的網(wǎng)絡(luò)環(huán)境。建議開(kāi)發(fā)者不要簡(jiǎn)單地將ATS禁用,而應(yīng)該升級(jí)服務(wù)器的配置支持ATS,為用戶提供更安全的服務(wù)。
4. 調(diào)試SSL/TLS
開(kāi)發(fā)一個(gè)新的App,通常終端和后端先協(xié)商好了具體業(yè)務(wù)邏輯的通信協(xié)議,后端和終端按照協(xié)議實(shí)現(xiàn)邏輯之后,就進(jìn)入聯(lián)調(diào)階段,第一次聯(lián)調(diào)往往會(huì)回到很多問(wèn)題,包括數(shù)據(jù)格式不對(duì),缺少基礎(chǔ)字段等;假如是基于HTTPS的網(wǎng)絡(luò)請(qǐng)求,則很可能由于后臺(tái)配置問(wèn)題,導(dǎo)致遇到如CFNetwork SSLHandshake failed (-9824)這類握手失敗的錯(cuò)誤。面對(duì)這類SSL錯(cuò)誤,該如何來(lái)解決呢?根據(jù)本人經(jīng)驗(yàn),主要是分兩步:
4.1 錯(cuò)誤碼
這會(huì)不會(huì)太簡(jiǎn)單了?其實(shí)最簡(jiǎn)單的往往是最有效的。SSL相關(guān)錯(cuò)誤碼可以在<Security/SecureTransport.h>中找到。上面-9824的錯(cuò)誤,對(duì)應(yīng)的是errSSLPeerHandshakeFail = -9824, /* handshake failure */,其他常見(jiàn)的錯(cuò)誤碼還有:
<figure class="highlight" style="box-sizing: border-box; color: rgb(0, 0, 0); font-family: "PingFang SC", "Source Han Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 1px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px;">
//...
/* fatal errors detected by peer */
errSSLPeerUnexpectedMsg = -9819, /* unexpected message received */
errSSLPeerBadRecordMac = -9820, /* bad MAC */
errSSLPeerDecryptionFail = -9821, /* decryption failed */
errSSLPeerRecordOverflow = -9822, /* record overflow */
errSSLPeerDecompressFail = -9823, /* decompression failure */
errSSLPeerHandshakeFail = -9824, /* handshake failure */
errSSLPeerBadCert = -9825, /* misc. bad certificate */
errSSLPeerUnsupportedCert = -9826, /* bad unsupported cert format */
errSSLPeerCertRevoked = -9827, /* certificate revoked */
errSSLPeerCertExpired = -9828, /* certificate expired */
errSSLPeerCertUnknown = -9829, /* unknown certificate */
errSSLIllegalParam = -9830, /* illegal parameter */
errSSLPeerUnknownCA = -9831, /* unknown Cert Authority */
errSSLPeerAccessDenied = -9832, /* access denied */
/* more errors detected by us */
errSSLHostNameMismatch = -9843, /* peer host name mismatch */
errSSLConnectionRefused = -9844, /* peer dropped connection before responding */
errSSLDecryptionFail = -9845, /* decryption failure */
errSSLBadRecordMac = -9846, /* bad MAC */
errSSLRecordOverflow = -9847, /* record overflow */
errSSLBadConfiguration = -9848, /* configuration error */
//...
</figure>
但靠錯(cuò)誤碼只能判斷大概的情況,很多時(shí)候并不能明確知道到底是什么原因?qū)е碌?,所以最直觀的,還是需要抓包分析。
4.2 抓包分析
在這一階段,使用Charles來(lái)抓包是沒(méi)有用的,因?yàn)镃harles是作為HTTP代理工作的,它會(huì)抓取代理的網(wǎng)絡(luò)報(bào)文,然后將報(bào)文組合成HTTP/HTTPS協(xié)議包,對(duì)于HTTP調(diào)試非常方便,但由于細(xì)節(jié)的缺失,沒(méi)辦法使用它來(lái)分析SSL相關(guān)錯(cuò)誤。所以我們需要使用上古神器Wireshark。
關(guān)于Wireshark就不再多介紹了,網(wǎng)上已經(jīng)有很多相關(guān)介紹和抓包教程,如《Mac OS X上使用Wireshark抓包》等,基本上可以很快上手。下面我們就以適配iOS9的ATS為例,來(lái)說(shuō)下如何進(jìn)行抓包分析,找出因?yàn)椴恢С諥TS導(dǎo)致SSL握手失敗問(wèn)題。
還記得SSL握手過(guò)程么?不記得可以重溫下這篇文章:圖解SSL/TLS協(xié)議。我們也來(lái)看看Wireshark上抓取到的包來(lái)直觀學(xué)習(xí)正常的SSL握手流程:

上圖是一個(gè)標(biāo)準(zhǔn)的HTTPS請(qǐng)求抓取的包:
- 在TCP三次握手成功之后,客戶端發(fā)起SSL的
Client Hello(No.68幀),傳遞隨機(jī)數(shù)(Random),和客戶端支持的加密套件(Cipher Suites)、壓縮方法、簽名算法等信息; 如下圖所示,這是Client Hello所攜帶的信息,可以展開(kāi)來(lái)看相關(guān)的詳情:

- 服務(wù)器從
Client Hello中匹配支持的加密套件(Cipher Suites)、壓縮算法和簽名算法,和服務(wù)器新生成的一個(gè)隨機(jī)數(shù)返回給客戶端,這就是Server Hello(No.70幀)。 下圖就是對(duì)1)中Client Hello的回應(yīng),由圖可以看出,服務(wù)端匹配的Cipher Suite是TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:

- 服務(wù)器同時(shí)會(huì)將證書(shū)發(fā)給客戶端(No.73幀);有時(shí)候抓取的包只有
Client Hello和Server Hello,而沒(méi)有再發(fā)送證書(shū)的,這是SSL/TLS的Session重用了:由于新建立一個(gè)SSL/TLS Session的成本太高,所以之前有建立SSL/TLS連接Session的話,客戶端會(huì)保存Session ID,在下一次請(qǐng)求時(shí)在Client Hello中帶上,服務(wù)端驗(yàn)證有效之后,就會(huì)成功重用Sesssion。
注:關(guān)于重用TLS Session,在特定場(chǎng)景下會(huì)引發(fā)嚴(yán)重的問(wèn)題:當(dāng)App只針對(duì)了代碼中發(fā)起的HTTPS請(qǐng)求做了本地證書(shū)校驗(yàn),而WebView中發(fā)起的HTTPS請(qǐng)求并沒(méi)有做本地證書(shū)校驗(yàn),可能會(huì)出現(xiàn)App內(nèi)代碼發(fā)起的請(qǐng)求直接重用WebView中建立的HTTPS鏈接,導(dǎo)致中間人可以實(shí)現(xiàn)短暫的繞過(guò)攻擊。
拓展閱讀:
- RFC5246#Handshake Protocol Overview查看Handshake的流程和相關(guān)信息。
- Apple官方開(kāi)發(fā)文檔:TLS Session Cache
客戶端確認(rèn)證書(shū)有效,則會(huì)生產(chǎn)最后一個(gè)隨機(jī)數(shù)(Premaster secret),并使用證書(shū)的公鑰RSA加密這個(gè)隨機(jī)數(shù),發(fā)回給服務(wù)端。為了更高的安全性,會(huì)改為Diffie-Hellman算法(簡(jiǎn)稱DH算法);采用DH算法,最后一個(gè)隨機(jī)數(shù)(Premaster secret)是不需要傳遞的,客戶端和服務(wù)端交換參數(shù)之后就可以算出。
Client Key Exchange(No. 75幀);接下來(lái)雙方都會(huì)發(fā)送
Change Cipher Spec通知對(duì)方,接下來(lái)的所有消息都會(huì)使用簽名約定好的密鑰進(jìn)行加密通信。最后是雙方的
Finished Message(即Encrypted Handshake Message, No. 77、79幀),這個(gè)消息是最終的校驗(yàn),里面包含了握手過(guò)程中的Session Key等信息,如果對(duì)方能夠解密這個(gè)消息則表示握手成功,結(jié)束整個(gè)SSL Handshake流程。
掌握了SSL/TLS握手流程之后,調(diào)試SSL/TLS就會(huì)變得非常簡(jiǎn)單,只需要看在哪個(gè)環(huán)節(jié)報(bào)錯(cuò)(Alert),就可以基本推斷出相關(guān)的錯(cuò)誤。
相關(guān)SSL/TLS接口信息,請(qǐng)查看:RFC5246以及SSL/TLS in Detail
下面就列舉下調(diào)試適配ATS過(guò)程中遇到的主要問(wèn)題:
- 加密套件(Cipher Suite)等參數(shù)無(wú)法匹配:加密套件不匹配是最常見(jiàn)的握手失敗的例子。
在ATS中,可接受的加密套件有包括:
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
但往往很多服務(wù)器的HTTPS配置很久沒(méi)有升級(jí),沒(méi)辦法支持這些Cipher Suite;客戶端發(fā)送Client Hello給服務(wù)端,帶上支持加密套件參數(shù);服務(wù)端查看這些參數(shù),發(fā)現(xiàn)一個(gè)都不支持,則直接返回Handshake Failure的信息。如下圖:

一般在接受到客戶端發(fā)送的Client Hello后返回Handshake Failure,都是因?yàn)榉?wù)端無(wú)法匹配客戶端SSL握手參數(shù)。至于是不是加密套件這個(gè)參數(shù)匹配的問(wèn)題,建議抓取取消ATS了的正常HTTPS請(qǐng)求包進(jìn)行對(duì)比,找出具體不匹配的參數(shù)。
SSL/TLS版本過(guò)低,這個(gè)也非常常見(jiàn),但一般會(huì)被上一個(gè)參數(shù)不匹配的錯(cuò)誤所掩蓋。因?yàn)榇蠖鄶?shù)SSL/TLS版本低的服務(wù)器HTTPS配置支持的加密套件等參數(shù)版本也比較低,而SSL/TLS版本是客戶端收到
Server Hello之后才驗(yàn)證的,但前面握手失敗就走不到這一步了。所以加密套件(Cipher Suite)等參數(shù)無(wú)法匹配支持,一般也就意味著服務(wù)端SSL/TLS版本過(guò)低。證書(shū)鏈配置錯(cuò)誤:在開(kāi)發(fā)過(guò)程中,本人遇到過(guò)證書(shū)鏈沒(méi)有按照順序進(jìn)行配置的問(wèn)題,也遇到過(guò)只配置了葉子證書(shū)的問(wèn)題。對(duì)于這些問(wèn)題,可以直接查看SSL握手過(guò)程中,服務(wù)端返回的
Certificate包:

上圖可以看到證書(shū)鏈Certificates只有一個(gè),這是典型的配置錯(cuò)誤。
PS:使用Wireshark進(jìn)行抓包的時(shí)候,有時(shí)候由于一些HTTPS請(qǐng)求的SSL/TLS版本號(hào)太低,Wireshark沒(méi)辦法辨認(rèn)其是SSL包,而是顯示為TCP;此時(shí)可以手動(dòng)來(lái)Decode:選擇對(duì)應(yīng)的TCP數(shù)據(jù)幀,右鍵 -》Decode As -》Transport 選擇SSL -》Apply既可。

5. 后記
這個(gè)時(shí)代,安全重要么?這是我曾常疑惑的。90%以上的大眾對(duì)安全沒(méi)有切實(shí)的概念,即使安全上了春晚,過(guò)了熱潮一切又重歸原樣。特別最近換工作到保險(xiǎn)金融類公司,安全問(wèn)題更是觸目驚心。一直相信,人如同一個(gè)圓,你知道的越多,學(xué)的越深,接觸的越廣,圓就越大,越知道自己的渺小,越懂得敬畏。
這世界永遠(yuǎn)不會(huì)缺少矛和盾,沒(méi)有“Mission Impossible”,不是么?