導(dǎo)語
在 WWDC 16 中,Apple 表示, 從 2017年1月1日起(最新消息, 實(shí)施時(shí)間已延期),所有新提交的 App 使用系統(tǒng)組件進(jìn)行的 HTTP 網(wǎng)絡(luò)請(qǐng)求都需要是 HTTPS 加密的,否則會(huì)導(dǎo)致請(qǐng)求失敗而無法通過審核。
精神哥對(duì) HTTPS 的驗(yàn)證過程有一些了解,但對(duì)于在iOS中如何實(shí)現(xiàn) HTTPS 驗(yàn)證卻不是很清楚,在內(nèi)網(wǎng)搜索到李晴同學(xué)寫的這篇文章,閱讀后收獲不小,分享給大家。
正文
本文的目的:一是簡(jiǎn)要分析下對(duì)服務(wù)器身份驗(yàn)證的完整握手過程,二是證書鏈的驗(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é)議用來通知對(duì)端從handshake切換到record協(xié)議(有點(diǎn)冗余,在TLS1.3里面已經(jīng)被刪掉了)。alert協(xié)議,用來通知各種返回碼。application data協(xié)議,就是把http,smtp等的數(shù)據(jù)流傳入record層做處理并傳輸。
想象一種場(chǎng)景:通常我們會(huì)訪問HTTPS://xxx的網(wǎng)站,當(dāng)你在瀏覽器地址欄輸入支持HTTPS協(xié)議的URL地址后,服務(wù)器返回的數(shù)據(jù)會(huì)顯示在頁(yè)面上。對(duì)于不了解HTTPS協(xié)議工作原理的小伙伴可能覺得這個(gè)過程很簡(jiǎn)單:發(fā)送請(qǐng)求-服務(wù)器響應(yīng)請(qǐng)求-結(jié)果返回并顯示。但對(duì)于HTTPS而言,在整個(gè)發(fā)送請(qǐng)求返回?cái)?shù)據(jù)過程中還涉及到通訊雙方證書驗(yàn)證、數(shù)據(jù)加密、數(shù)據(jù)完整性校驗(yàn)等。
下面以登錄qq郵箱為例,通過Wireshark抓包可以看到如下圖:

在瀏覽器與服務(wù)器進(jìn)行Application Data傳輸之前,還經(jīng)歷了Client Hello-Server Hello-Client Key Exchange-Change Cipher Spec等過程。而這些過程正是TLS/SSL提供的服務(wù)所決定的:
- 認(rèn)證服務(wù)器身份,確保數(shù)據(jù)發(fā)送到正確的服務(wù)器;
- 加密數(shù)據(jù)以防止數(shù)據(jù)中途被竊?。?/li>
- 維護(hù)數(shù)據(jù)的完整性,確保數(shù)據(jù)在傳輸過程中不被改變。
上述單向驗(yàn)證的完整握手過程,總結(jié)如下:

第一階段:ClientHello
客戶端發(fā)起請(qǐng)求,以明文傳輸請(qǐng)求信息,包含版本信息,加密套件候選列表,壓縮算法候選列表,隨機(jī)數(shù)random_C,擴(kuò)展字段等信息。
第二階段:ServerHello-ServerHelloDone
如上圖可以看出這個(gè)階段包含4個(gè)過程( 有的服務(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)證通過后,才能進(jìn)行后續(xù)的通信。(具體分析可參看后續(xù)的證書驗(yàn)證過程)
第四階段: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)單來說,HTTPS請(qǐng)求整個(gè)過程主要分為兩部分。一是握手過程:用于客戶端和服務(wù)器驗(yàn)證雙方身份,協(xié)商后續(xù)數(shù)據(jù)傳輸時(shí)使用到的密鑰等。二是數(shù)據(jù)傳輸過程:身份驗(yàn)證通過并協(xié)商好密鑰后,通信雙方使用協(xié)商好的密鑰加密數(shù)據(jù)并進(jìn)行通信。在握手過程協(xié)商密鑰時(shí),使用的是非對(duì)稱密鑰交換算法, 密鑰交換算法本身非常復(fù)雜,密鑰交換過程涉及到隨機(jī)數(shù)生成,模指數(shù)運(yùn)算,空白補(bǔ)齊,加密,簽名等操作。在數(shù)據(jù)傳輸過程中,客戶端和服務(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)過程
- 證書申請(qǐng)者向頒發(fā)證書的可信第三方CA提交申請(qǐng)證書相關(guān)信息,包括:申請(qǐng)者域名、申請(qǐng)者生成的公鑰(私鑰自己保存)及證書請(qǐng)求文件.cer等
- CA通過線上、線下等多種手段驗(yàn)證證書申請(qǐng)者提供的信息合法和真實(shí)性。
- 當(dāng)證書申請(qǐng)者提供的信息審核通過后,CA向證書申請(qǐng)者頒發(fā)證書,證書內(nèi)容包括明文信息和簽名信息。其中明文信息包括證書頒發(fā)機(jī)構(gòu)、證書有效期、域名、申請(qǐng)者相關(guān)信息及申請(qǐng)者公鑰等,簽名信息是使用CA私鑰進(jìn)行加密的明文信息。當(dāng)證書申請(qǐng)者獲取到證書后,可以通過安裝的CA證書中的公鑰對(duì)簽名信息進(jìn)行解密并與明文信息進(jìn)行對(duì)比來驗(yàn)證簽名的完整性。
證書驗(yàn)證過程
- 驗(yàn)證證書本身的合法性(驗(yàn)證簽名完整性,驗(yàn)證證書有效期等)
- 驗(yàn)證證書頒發(fā)者的合法性(查找頒發(fā)者的證書并檢查其合法性,這個(gè)過程是遞歸的)
證書驗(yàn)證的遞歸過程最終會(huì)成功終止,而成功終止的條件是:證書驗(yàn)證過程中遇到了錨點(diǎn)證書,錨點(diǎn)證書通常指:嵌入到操作系統(tǒng)中的根證書(權(quán)威證書頒發(fā)機(jī)構(gòu)頒發(fā)的自簽名證書)。
證書驗(yàn)證失敗的原因
- 無法找到證書的頒發(fā)者
- 證書過期
- 驗(yàn)證過程中遇到了自簽名證書,但該證書不是錨點(diǎn)證書。
- 無法找到錨點(diǎ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)(即訪問拒絕)。此時(shí)NSURLConnection或NSURLSession會(huì)接收到服務(wù)器需要授權(quán)的響應(yīng),當(dāng)客戶端授權(quán)通過后,才能繼續(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)證通過時(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)證。

- sender:發(fā)送者,在此指的是self.connection

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)證通過的信任對(duì)象,客戶端也可以不提供證書憑證。
- 對(duì)于NSURLSession,傳遞如下之一的值給completion handler回調(diào):
- NSURLSessionAuthChallengePerformDefaultHandling處理請(qǐng)求,就好像代理沒有提供一個(gè)代理方法來處理認(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通過本地導(dǎo)入的證書來驗(yàn)證服務(wù)器證書是否是可信的。如果服務(wù)器證書是這個(gè)錨點(diǎn)證書對(duì)應(yīng)CA或者子CA頒發(fā)的,或服務(wù)器證書本身就是這個(gè)錨點(diǎn)證書,則證書信任通過。如下代碼(參考文檔):

自簽名證書驗(yàn)證實(shí)現(xiàn)
對(duì)于自簽名證書,這樣Trust Object中的服務(wù)器證書是不可信任的CA頒發(fā)的,直接使用SecTrustEvaluate驗(yàn)證是不會(huì)成功的。可以采取下述簡(jiǎn)單代碼繞過HTTPS的驗(yàn)證:

上述代碼一般用于當(dāng)服務(wù)器使用自簽名證書時(shí),為了方便測(cè)試,客戶端可以通過該方法信任所有自簽名證書。
綜上對(duì)非自建和自建證書驗(yàn)證過程的分析,可以總結(jié)如下:
- 獲取需要驗(yàn)證的信任對(duì)象(Trust Object)。對(duì)于NSURLConnection來說, 是從delegate方法-connection: willSendRequestForAuthenticationChallenge:回調(diào)回來的參數(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)證通過了,一般的安全要求下,就可以直接驗(yàn)證通過,進(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)的信任列表中。