打造安全的App!iOS安全系列之 HTTPS 進(jìn)階
本文分為以下四節(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í),有點(diǎn)干貨。
1. 中間人攻擊
關(guān)于HTTPS,我經(jīng)常會(huì)提到的就是中間人攻擊,那究竟什么是中間人攻擊呢?中間人攻擊,即所謂的Main-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(最低TLS1);
-
禁用一些已爆出安全隱患的加密方法;
-
使用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)了欺騙。可以使用Mac 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ū)不是偽造的;
#p#
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確有畫(huà)蛇添足之嫌。首先我們查看下系統(tǒng)的默認(rèn)校驗(yàn)策略:
- - (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);
- //...
- }
打印默認(rèn)校驗(yàn)策略信息:
- 5 :{contents = "ValidRoot"} ={value = true}
- 6 :{contents = "SSLHostname"} ={contents = "xxx.xxx.com"}
- 8 :{contents = "ValidLeaf"} ={value = true}
從打印信息來(lái)看,系統(tǒng)的默認(rèn)校驗(yàn)策略中已包含了域名校驗(yàn)。然后再看AFSecurityPolicy中相關(guān)源碼:
- - (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);
- //...
- }
這其實(shí)也是很多開(kāi)發(fā)者在處理異常與默認(rèn)邏輯分支時(shí)會(huì)犯的錯(cuò)誤,這段邏輯推薦實(shí)現(xiàn)方式是:
- //取代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);
- }
- //...
- }
從代碼上看,邏輯是否變得更清晰了?而且也表明系統(tǒng)默認(rèn)的校驗(yàn)方式是會(huì)驗(yàn)證域名的。實(shí)際上SecTrustSetPolicies重新設(shè)置校驗(yàn)策略是用于使用IP進(jìn)行HTTPS請(qǐng)求,或者一個(gè)證書(shū)用于多個(gè)域名的場(chǎng)景;在這些場(chǎng)景下,服務(wù)器證書(shū)上的域名和請(qǐng)求域名就會(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ì)比,只有本地打包好的證書(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ā)的;這些就是系統(tǒng)驗(yàn)證的步驟,也就是系統(tǒng)確人使用該證書(shū)的服務(wù)器是請(qǐng)求域名對(duì)應(yīng)的服務(wù)器。經(jīng)過(guò)系統(tǒng)驗(yàn)證通過(guò)的證書(shū)鏈,兩端被確認(rèn)可信,那中間的任一節(jié)點(diǎn)也是可信的,不需要把中間節(jié)點(diǎn)都一一對(duì)比驗(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ì)被信任。所以我們不能完全相信系統(tǒng)的校驗(yàn),因?yàn)橄到y(tǒng)的校驗(yàn)對(duì)比的源很可能被污染了。
所以還是需要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>
1) 一般葉子證書(shū)的有效期都比較短,Google和Baidu官網(wǎng)證書(shū)的有效期也就幾個(gè)月;而App由于是客戶端,需要一定的向后兼容,稍疏于檢查,今天發(fā)布,過(guò)兩天證書(shū)就過(guò)期了。
2) 越往證書(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ū),暫時(shí)并沒(méi)有定論。
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: 1) The server must support at least Transport Layer Security (TLS) protocol version 1.2. 2) Connection ciphers are limited to those that provide forward secrecy (see the list of ciphers below.) 3) 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。
當(dāng)前日益復(fù)雜脆弱的網(wǎng)絡(luò)難以保證用戶的數(shù)據(jù)安全,因此Apple才在iOS9上強(qiáng)推ATS,反向逼迫服務(wù)器升級(jí)HTTPS的配置,已提供更安全的網(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ò)誤碼可以在
- //...
- /* 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 */
- //...
但靠錯(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)求抓取的包:
1) 在TCP三次握手成功之后,客戶端發(fā)起SSL的Client Hello(No.68幀),傳遞隨機(jī)數(shù)(Random),和客戶端支持的密碼套件(Cipher Suites)、壓縮方法、簽名算法等信息; 如下圖所示,這是Client Hello所攜帶的信息,可以展開(kāi)來(lái)看相關(guān)的詳情:
2) 服務(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:
3) 服務(wù)器同時(shí)會(huì)將證書(shū)發(fā)給客戶端(No.73幀);
4) 客戶端確認(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幀);
5) 接下來(lái)雙方都會(huì)發(fā)送Change Cipher Spec通知對(duì)方,接下來(lái)的所有消息都會(huì)使用簽名約定好的密鑰進(jìn)行加密通信。
6) 最后是雙方的Finished Message(即Encrypted Handshake Message, No. 77、79幀),這個(gè)消息是最終的校驗(yàn),里面包含了握手過(guò)程中的Session Key等信息,如果對(duì)方能夠解密這個(gè)消息則表示握手成功,結(jié)束整個(gè)SSL Handshake流程。
相關(guān)SSL/TLS接口信息,請(qǐng)查看:rfc5246以及SSL/TLS in Detail.aspx)
上面已抓取的HTTPS請(qǐng)求為例,簡(jiǎn)單介紹了SSL/TLS的握手流程。下面就列舉下調(diào)試適配ATS過(guò)程中遇到的主要問(wèn)題:
1) 密碼套件(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ù)。
2) 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ò)低。
3) 證書(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包,而是顯示為T(mén)CP;此時(shí)可以手動(dòng)來(lái)Decode:選擇對(duì)應(yīng)的TCP數(shù)據(jù)幀,右鍵 -》Decode As -》Transport 選擇SSL -》Apply既可。