本文來(lái)自于騰訊Bugly公眾號(hào)(weixinBugly)蹋绽,原文地址:https://mp.weixin.qq.com/s/-fLLTtip509K6pNOTkflPQ
本文的目的:一是簡(jiǎn)要分析下對(duì)服務(wù)器身份驗(yàn)證的完整握手過(guò)程,二是證書鏈的驗(yàn)證脊凰,三是探索下iOS中原生庫(kù)NSURLConnection或NSURLSession如何支持實(shí)現(xiàn)https卡儒。
一蛆橡、HTTPS
HTTPS是承載在TLS/SSL之上的HTTP拱绑,相較于HTTP明文數(shù)據(jù)傳輸方面所暴露出的缺點(diǎn),HTTPS具有防止信息被竊聽九妈、篡改朴恳、劫持,提供信息加密允蚣,完整性校驗(yàn)及身份驗(yàn)證等優(yōu)勢(shì)于颖。TLS/SSL是安全傳輸層協(xié)議,介于TCP和HTTP之間嚷兔。TLS1.0是建立在SSL3.0規(guī)范之上的森渐,可以理解為SSL3.0的升級(jí)版本。目前推薦使用的版本是TLS1.2冒晰。
TLS/SSL協(xié)議通常分為兩層:TLS記錄協(xié)議(TLS Record Protocol)和TLS握手協(xié)議(TLS Handshake Protocol)同衣。 TLS記錄協(xié)議建立在可靠的傳輸協(xié)議(如TCP)之上,為高層協(xié)議提供數(shù)據(jù)封裝壶运、壓縮耐齐、加密等基本功能的支持。TLS握手協(xié)議建立在記錄協(xié)議之上蒋情,用于在實(shí)際的數(shù)據(jù)傳輸開始前埠况,通訊雙方進(jìn)行身份認(rèn)證、協(xié)商加密算法棵癣、交換加密密鑰等辕翰。除了這倆協(xié)議以外,還存在其它三種輔助協(xié)議: Changecipher spec 協(xié)議用來(lái)通知對(duì)端從handshake切換到record協(xié)議(有點(diǎn)冗余狈谊,在TLS1.3里面已經(jīng)被刪掉了)喜命。alert協(xié)議,用來(lái)通知各種返回碼河劝。application data協(xié)議壁榕,就是把http,smtp等的數(shù)據(jù)流傳入record層做處理并傳輸赎瞎。
想象一種場(chǎng)景:通常我們會(huì)訪問(wèn)HTTPS://xxx的網(wǎng)站牌里,當(dāng)你在瀏覽器地址欄輸入支持HTTPS協(xié)議的URL地址后,服務(wù)器返回的數(shù)據(jù)會(huì)顯示在頁(yè)面上煎娇。對(duì)于不了解HTTPS協(xié)議工作原理的小伙伴可能覺得這個(gè)過(guò)程很簡(jiǎn)單:發(fā)送請(qǐng)求-服務(wù)器響應(yīng)請(qǐng)求-結(jié)果返回并顯示二庵。但對(duì)于HTTPS而言,在整個(gè)發(fā)送請(qǐng)求返回?cái)?shù)據(jù)過(guò)程中還涉及到通訊雙方證書驗(yàn)證缓呛、數(shù)據(jù)加密催享、數(shù)據(jù)完整性校驗(yàn)等。
下面以登錄qq郵箱為例哟绊,通過(guò)Wireshark抓包可以看到如下圖:
在瀏覽器與服務(wù)器進(jìn)行Application Data傳輸之前因妙,還經(jīng)歷了Client Hello-Server Hello-Client Key Exchange-Change Cipher Spec等過(guò)程。而這些過(guò)程正是TLS/SSL提供的服務(wù)所決定的:
認(rèn)證服務(wù)器身份,確保數(shù)據(jù)發(fā)送到正確的服務(wù)器攀涵;
加密數(shù)據(jù)以防止數(shù)據(jù)中途被竊认吃拧;
維護(hù)數(shù)據(jù)的完整性以故,確保數(shù)據(jù)在傳輸過(guò)程中不被改變蜗细。
上述單向驗(yàn)證的完整握手過(guò)程,總結(jié)如下:
第一階段:ClientHello
客戶端發(fā)起請(qǐng)求怒详,以明文傳輸請(qǐng)求信息炉媒,包含版本信息,加密套件候選列表昆烁,壓縮算法候選列表吊骤,隨機(jī)數(shù)random_C,擴(kuò)展字段等信息静尼。
第二階段:ServerHello-ServerHelloDone
如上圖可以看出這個(gè)階段包含4個(gè)過(guò)程( 有的服務(wù)器是單條發(fā)送白粉,有的是合并一起發(fā)送)。服務(wù)端返回協(xié)商的信息結(jié)果鼠渺,包括選擇使用的協(xié)議版本鸭巴,選擇的加密套件,選擇的壓縮算法系冗、隨機(jī)數(shù)random_S等奕扣,其中隨機(jī)數(shù)用于后續(xù)的密鑰協(xié)商。服務(wù)器也會(huì)配置并返回對(duì)應(yīng)的證書鏈Certificate掌敬,用于身份驗(yàn)證與密鑰交換。然后會(huì)發(fā)送ServerHelloDone信息用于通知服務(wù)器信息發(fā)送結(jié)束池磁。
第三階段:證書校驗(yàn)
在上圖中的5-6之間奔害,客戶端這邊還需要對(duì)服務(wù)器返回的證書進(jìn)行校驗(yàn)。只有證書驗(yàn)證通過(guò)后地熄,才能進(jìn)行后續(xù)的通信华临。(具體分析可參看后續(xù)的證書驗(yàn)證過(guò)程)
第四階段:ClientKeyExchange-Finished
服務(wù)器返回的證書驗(yàn)證合法后, 客戶端計(jì)算產(chǎn)生隨機(jī)數(shù)字Pre-master端考,并用server證書中公鑰加密雅潭,發(fā)送給服務(wù)器。同時(shí)客戶端會(huì)根據(jù)已有的三個(gè)隨機(jī)數(shù)根據(jù)相應(yīng)的生成協(xié)商密鑰却特》龉客戶端會(huì)通知服務(wù)器后續(xù)的通信都采用協(xié)商的通信密鑰和加密算法進(jìn)行加密通信。然后客戶端發(fā)送Finished消息用于通知客戶端信息發(fā)送結(jié)束裂明。
第五階段:服務(wù)器端生成協(xié)商密鑰
服務(wù)器也會(huì)根據(jù)已有的三個(gè)隨機(jī)數(shù)使用相應(yīng)的算法生成協(xié)商密鑰椿浓,會(huì)通知客戶端后續(xù)的通信都采用協(xié)商的通信密鑰和加密算法進(jìn)行加密通信。然后發(fā)送Finished消息用于通知服務(wù)器信息發(fā)送結(jié)束。
第六階段:握手結(jié)束
在握手階段結(jié)束后扳碍,客戶端和服務(wù)器數(shù)據(jù)傳輸開始使用協(xié)商密鑰進(jìn)行加密通信提岔。
總結(jié)
簡(jiǎn)單來(lái)說(shuō),HTTPS請(qǐng)求整個(gè)過(guò)程主要分為兩部分笋敞。一是握手過(guò)程:用于客戶端和服務(wù)器驗(yàn)證雙方身份碱蒙,協(xié)商后續(xù)數(shù)據(jù)傳輸時(shí)使用到的密鑰等。二是數(shù)據(jù)傳輸過(guò)程:身份驗(yàn)證通過(guò)并協(xié)商好密鑰后夯巷,通信雙方使用協(xié)商好的密鑰加密數(shù)據(jù)并進(jìn)行通信赛惩。在握手過(guò)程協(xié)商密鑰時(shí),使用的是非對(duì)稱密鑰交換算法鞭莽, 密鑰交換算法本身非常復(fù)雜坊秸,密鑰交換過(guò)程涉及到隨機(jī)數(shù)生成,模指數(shù)運(yùn)算澎怒,空白補(bǔ)齊褒搔,加密,簽名等操作喷面。在數(shù)據(jù)傳輸過(guò)程中星瘾,客戶端和服務(wù)器端使用協(xié)商好的密鑰進(jìn)行對(duì)稱加密解密。
二惧辈、證書
PKI (Public Key Infrastructure)琳状,公開密鑰基礎(chǔ)設(shè)施。它是一個(gè)標(biāo)準(zhǔn),在這個(gè)標(biāo)準(zhǔn)之下發(fā)展出的為了實(shí)現(xiàn)安全基礎(chǔ)服務(wù)目的的技術(shù)統(tǒng)稱為PKI盒齿。 權(quán)威的第三方機(jī)構(gòu)CA(認(rèn)證中心)是PKI的核心念逞, CA負(fù)責(zé)核實(shí)公鑰的擁有者的信息,并頒發(fā)認(rèn)證”證書”边翁,同時(shí)能夠?yàn)槭褂谜咛峁┳C書驗(yàn)證服務(wù)翎承。 x.509是PKI中最重要的標(biāo)準(zhǔn),它定義了公鑰證書的基本結(jié)構(gòu)符匾。
證書申請(qǐng)過(guò)程
證書申請(qǐng)者向頒發(fā)證書的可信第三方CA提交申請(qǐng)證書相關(guān)信息叨咖,包括:申請(qǐng)者域名、申請(qǐng)者生成的公鑰(私鑰自己保存)及證書請(qǐng)求文件.cer等
CA通過(guò)線上啊胶、線下等多種手段驗(yàn)證證書申請(qǐng)者提供的信息合法和真實(shí)性甸各。
當(dāng)證書申請(qǐng)者提供的信息審核通過(guò)后,CA向證書申請(qǐng)者頒發(fā)證書焰坪,證書內(nèi)容包括明文信息和簽名信息趣倾。其中明文信息包括證書頒發(fā)機(jī)構(gòu)、證書有效期琳彩、域名誊酌、申請(qǐng)者相關(guān)信息及申請(qǐng)者公鑰等部凑,簽名信息是使用CA私鑰進(jìn)行加密的明文信息。當(dāng)證書申請(qǐng)者獲取到證書后碧浊,可以通過(guò)安裝的CA證書中的公鑰對(duì)簽名信息進(jìn)行解密并與明文信息進(jìn)行對(duì)比來(lái)驗(yàn)證簽名的完整性涂邀。
證書驗(yàn)證過(guò)程
驗(yàn)證證書本身的合法性(驗(yàn)證簽名完整性,驗(yàn)證證書有效期等)
驗(yàn)證證書頒發(fā)者的合法性(查找頒發(fā)者的證書并檢查其合法性箱锐,這個(gè)過(guò)程是遞歸的)
證書驗(yàn)證的遞歸過(guò)程最終會(huì)成功終止比勉,而成功終止的條件是:證書驗(yàn)證過(guò)程中遇到了錨點(diǎn)證書,錨點(diǎn)證書通常指:嵌入到操作系統(tǒng)中的根證書(權(quán)威證書頒發(fā)機(jī)構(gòu)頒發(fā)的自簽名證書)驹止。
證書驗(yàn)證失敗的原因
無(wú)法找到證書的頒發(fā)者
證書過(guò)期
驗(yàn)證過(guò)程中遇到了自簽名證書浩聋,但該證書不是錨點(diǎn)證書。
無(wú)法找到錨點(diǎn)證書(即在證書鏈的頂端沒有找到合法的根證書)
訪問(wèn)的server的dns地址和證書中的地址不同
三臊恋、iOS實(shí)現(xiàn)支持HTTPS
在OC中當(dāng)使用NSURLConnection或NSURLSession建立URL并向服務(wù)器發(fā)送https請(qǐng)求獲取資源時(shí)衣洁,服務(wù)器會(huì)使用HTTP狀態(tài)碼401進(jìn)行響應(yīng)(即訪問(wèn)拒絕)。此時(shí)NSURLConnection或NSURLSession會(huì)接收到服務(wù)器需要授權(quán)的響應(yīng)抖仅,當(dāng)客戶端授權(quán)通過(guò)后坊夫,才能繼續(xù)從服務(wù)器獲取數(shù)據(jù)。如下圖所示:
非自簽名證書驗(yàn)證實(shí)現(xiàn)
在接收到服務(wù)器返回的狀態(tài)碼為401的響應(yīng)后撤卢,對(duì)于NSURLSession而言环凿,需要代理對(duì)象實(shí)現(xiàn)URLSession:task:didReceiveChallenge:completionHandler:方法。對(duì)于NSURLConnection而言放吩,需要代理對(duì)象實(shí)現(xiàn)connection:willSendRequestForAuthenticationChallenge: 方法(OS X v10.7和iOS5及以上)智听,對(duì)于早期的版本代理對(duì)象需要實(shí)現(xiàn)代理對(duì)象要實(shí)現(xiàn)connection:canAuthenticateAgainstProtectionSpace:和connection:didReceiveAuthenticationChallenge:方法。代碼如下(參考文檔):
當(dāng)客戶端發(fā)送https請(qǐng)求后渡紫,服務(wù)器會(huì)返回需要授權(quán)的相關(guān)信息到推,然后connection:willSendRequestForAuthenticationChallenge:方法被調(diào)用√枧欤客戶端根據(jù)返回的challenge信息环肘,首先獲取需要驗(yàn)證的信任對(duì)象trust,然后調(diào)用SecTrustEvaluate方法是用系統(tǒng)默認(rèn)的驗(yàn)證方式對(duì)信任對(duì)象進(jìn)行驗(yàn)證,當(dāng)驗(yàn)證通過(guò)時(shí)集灌,使用該信任對(duì)象trust生成證書憑證,然后self.connection使用該憑證繼續(xù)連接复哆。如下詳解:
NSURLAuthenticationChallenge包含如下信息:
error :最后一次授權(quán)失敗的錯(cuò)誤信息
failureResponse :最后一次授權(quán)失敗的錯(cuò)誤信息
previousFailureCount :授權(quán)失敗的次數(shù)
proposedCredential :建議使用的證書
-
protectionSpace :NSURLProtectionSpace對(duì)象欣喧,代表了服務(wù)器上的一塊需要授權(quán)信息的區(qū)域。包括了服務(wù)器地址梯找、端口等信息唆阿。在此指的是challenge.protectionSpace。其中Auth-scheme指protectionSpace所支持的驗(yàn)證方法锈锤,NSURLAuthenticationMethodServerTrust指對(duì)protectionSpace執(zhí)行證書驗(yàn)證驯鳖。
image -
sender:發(fā)送者闲询,在此指的是self.connection
image
SecTrustRef
表示需要驗(yàn)證的信任對(duì)象(Trust Object),在此指的是challenge.protectionSpace.serverTrust浅辙。包含待驗(yàn)證的證書和支持的驗(yàn)證方法等扭弧。
SecTrustResultType
表示驗(yàn)證結(jié)果。其中 kSecTrustResultProceed表示serverTrust驗(yàn)證成功记舆,且該驗(yàn)證得到了用戶認(rèn)可(例如在彈出的是否信任的alert框中選擇always trust)鸽捻。 kSecTrustResultUnspecified表示 serverTrust驗(yàn)證成功,此證書也被暗中信任了泽腮,但是用戶并沒有顯示地決定信任該證書御蒲。 兩者取其一就可以認(rèn)為對(duì)serverTrust驗(yàn)證成功。
SecTrustEvaluate
函數(shù)內(nèi)部遞歸地從葉節(jié)點(diǎn)證書到根證書驗(yàn)證诊赊。使用系統(tǒng)默認(rèn)的驗(yàn)證方式驗(yàn)證Trust Object厚满,根據(jù)上述證書鏈的驗(yàn)證可知,系統(tǒng)會(huì)根據(jù)Trust Object的驗(yàn)證策略碧磅,一級(jí)一級(jí)往上碘箍,驗(yàn)證證書鏈上每一級(jí)證書有效性。
NSURLCredential
表示身份驗(yàn)證證書续崖。URL Lodaing支持3種類型證書:password-based user credentials, certificate-based user credentials, 和certificate-based server credentials(需要驗(yàn)證服務(wù)器身份時(shí)使用)敲街。因此NSURLCredential可以表示由用戶名/密碼組合、客戶端證書及服務(wù)器信任創(chuàng)建的認(rèn)證信息严望,適合大部分的認(rèn)證請(qǐng)求多艇。對(duì)于NSURLCredential也存在三種持久化機(jī)制:
NSURLCredentialPersistenceNone :要求 URL 載入系統(tǒng) “在用完相應(yīng)的認(rèn)證信息后立刻丟棄”。
NSURLCredentialPersistenceForSession :要求 URL 載入系統(tǒng) “在應(yīng)用終止時(shí)像吻,丟棄相應(yīng)的 credential ”峻黍。
NSURLCredentialPersistencePermanent :要求 URL 載入系統(tǒng) “將相應(yīng)的認(rèn)證信息存入鑰匙串(keychain),以便其他應(yīng)用也能使用拨匆。
對(duì)于已經(jīng)驗(yàn)證通過(guò)的信任對(duì)象姆涩,客戶端也可以不提供證書憑證。
對(duì)于NSURLSession惭每,傳遞如下之一的值給completion handler回調(diào):
NSURLSessionAuthChallengePerformDefaultHandling處理請(qǐng)求骨饿,就好像代理沒有提供一個(gè)代理方法來(lái)處理認(rèn)證請(qǐng)求
NSURLSessionAuthChallengeRejectProtectionSpace拒接認(rèn)證請(qǐng)求√ㄐ龋基于服務(wù)器響應(yīng)的認(rèn)證類型宏赘,URL加載類可能會(huì)多次調(diào)用代理方法。
對(duì)于 NSURLConnection 和 NSURLDownload黎侈,在[challenge sender] 上調(diào)用continueWithoutCredentialsForAuthenticationChallenge:方法察署。不提供證書的話,可能會(huì)導(dǎo)致連接失敗峻汉,調(diào)用connectionDidFailWithError:方法 贴汪,或者會(huì)返回一個(gè)不需要驗(yàn)證身份的替代的URL脐往。 如下代碼:
對(duì)于非自簽名的證書,即使服務(wù)器返回的證書是信任的CA頒發(fā)的扳埂,而為了確定返回的證書正是客戶端需要的證書业簿,這需要本地導(dǎo)入證書,并將證書設(shè)置成需要參與驗(yàn)證的錨點(diǎn)證書聂喇,再調(diào)用SecTrustEvaluate通過(guò)本地導(dǎo)入的證書來(lái)驗(yàn)證服務(wù)器證書是否是可信的辖源。如果服務(wù)器證書是這個(gè)錨點(diǎn)證書對(duì)應(yīng)CA或者子CA頒發(fā)的,或服務(wù)器證書本身就是這個(gè)錨點(diǎn)證書希太,則證書信任通過(guò)克饶。如下代碼(參考文檔):
自簽名證書驗(yàn)證實(shí)現(xiàn)
對(duì)于自簽名證書,這樣Trust Object中的服務(wù)器證書是不可信任的CA頒發(fā)的誊辉,直接使用SecTrustEvaluate驗(yàn)證是不會(huì)成功的矾湃。可以采取下述簡(jiǎn)單代碼繞過(guò)HTTPS的驗(yàn)證:
上述代碼一般用于當(dāng)服務(wù)器使用自簽名證書時(shí)堕澄,為了方便測(cè)試邀跃,客戶端可以通過(guò)該方法信任所有自簽名證書。
綜上對(duì)非自建和自建證書驗(yàn)證過(guò)程的分析蛙紫,可以總結(jié)如下:
獲取需要驗(yàn)證的信任對(duì)象(Trust Object)拍屑。對(duì)于NSURLConnection來(lái)說(shuō),
是從delegate方法-connection: willSendRequestForAuthenticationChallenge:回調(diào)回來(lái)的參數(shù)challenge中獲取(challenge.protectionSpace.serverTrust) 坑傅。使用系統(tǒng)默認(rèn)驗(yàn)證方式驗(yàn)證Trust Object僵驰。SecTrustEvaluate會(huì)根據(jù)Trust Object的驗(yàn)證策略,一級(jí)一級(jí)往上唁毒,驗(yàn)證證書鏈上每一級(jí)數(shù)字簽名的有效性蒜茴,從而評(píng)估證書的有效性。
如第二步驗(yàn)證通過(guò)了浆西,一般的安全要求下粉私,就可以直接驗(yàn)證通過(guò),進(jìn)入到下一步:使用Trust Object生成一份憑證([NSURLCredential credentialForTrust:serverTrust])近零,傳入challenge的sender中([challenge.sender useCredential:cred forAuthenticationChallenge:challenge])處理诺核,建立連接。
假如有更強(qiáng)的安全要求久信,可以繼續(xù)對(duì)Trust Object進(jìn)行更嚴(yán)格的驗(yàn)證猪瞬。常用的方式是在本地導(dǎo)入證書,驗(yàn)證Trust Object與導(dǎo)入的證書是否匹配入篮。
假如驗(yàn)證失敗,取消此次Challenge-Response Authentication驗(yàn)證流程幌甘,拒絕連接請(qǐng)求潮售。
假如是自建證書的痊项,則不使用第二步系統(tǒng)默認(rèn)的驗(yàn)證方式,因?yàn)樽越ㄗC書的根CA的數(shù)字簽名未在操作系統(tǒng)的信任列表中酥诽。