Alamofire 安全認(rèn)證ServerTrustPolicy

前言

在互聯(lián)網(wǎng)迅速發(fā)展的年代欧瘪,基本上天天都在跟網(wǎng)絡(luò)打交道葬毫。那么题篷,在網(wǎng)絡(luò)的通訊中怎么保證信息的安全性呢词身?這篇文章,我們就來講講悼凑,Alamofire作為iOS開發(fā)中一個非常優(yōu)秀的網(wǎng)絡(luò)請求相關(guān)的第三方庫偿枕,它的安全策略是怎么設(shè)計和使用的。

HTTPS簡介

在切入正題之前户辫,先來簡單的了解一下HTTPS相關(guān)知識渐夸,方便對后面內(nèi)容的理解。如果你已經(jīng)了解了渔欢,可以直接跳過這一段墓塌。

為什么使用HTTPS

在以前,我們用的更多的是HTTP奥额,那么是什么原因蘋果公司也主推我們使用HTTPS這個更安全請求方式的呢苫幢?HTTP存在的問題:

  1. 通訊使用明文,內(nèi)容容易被竊聽
  2. 不驗證通訊雙方的身份垫挨,容易被偽裝
  3. 無法驗證數(shù)據(jù)完整性韩肝,可能會被篡改數(shù)據(jù)

使用 HTTPS 通信機制可以有效地防止這些問題。HTTPS 并非是應(yīng)用層的一種新協(xié)議九榔,只是HTTP通信接口部分用 SSLTLS 協(xié)議代替而已哀峻。通常 HTTP 直接跟 TCP 通信涡相,當(dāng)使用 SSL 時,則變成先和 SSL 通信剩蟀,再由 SSLTCP 通信了催蝗。簡言之,HTTPS 就是身披 SSL 協(xié)議這層外殼的 HTTP育特。

HTTP+數(shù)據(jù)加密+身份認(rèn)證+數(shù)據(jù)完整性保護(hù)=HTTPS

HTTPS加密方式

HTTPS 采用混合加密機制丙号,就是共享秘鑰加密(對稱加密)和公開密鑰加密(非對稱加密)兩者并用的混合加密機制。在交換密鑰環(huán)節(jié)使用公開密鑰加密的方式缰冤,之后建立通訊交換報文階段則使用共享密鑰加密犬缨。公開密鑰加密比共享密鑰加密更加安全,那為什么不全用公開密鑰加密锋谐?因為與共享秘鑰加密相比遍尺,處理起來更復(fù)雜、處理速度慢涮拗。HTTPS結(jié)合了兩種加密方式的優(yōu)劣來實現(xiàn)一種混合加密的流程乾戏。如圖所示:

HTTPS驗證和加密流程

HTTPS有單向認(rèn)證和雙向認(rèn)證,原理基本差不多三热,這里就講一下單向認(rèn)證的整個流程鼓择,先看一張圖:

  1. 客戶端發(fā)起HTTPS請求:客戶端向服務(wù)端發(fā)送SSL協(xié)議版本號、加密算法種類就漾、隨機數(shù)等信息呐能。
  2. 服務(wù)端的配置:采用HTTPS協(xié)議的服務(wù)器必須要有一套數(shù)字證書,可以自己制作抑堡,也可以向組織申請摆出。區(qū)別就是自己頒發(fā)的證書需要客戶端驗證通過,才可以繼續(xù)訪問首妖,而使用受信任的公司申請的證書可以直接通過偎漫。這套證書其實就是一對公鑰和私鑰。
  3. 傳送證書:這個證書其實就是公鑰有缆,只是包含了很多信息象踊,如證書的頒發(fā)機構(gòu),過期時間等等棚壁。同時服務(wù)端也會向客戶端發(fā)送SSL協(xié)議版本號杯矩、加密算法種類、隨機數(shù)等信息
  4. 客戶端解析證書:這部分工作是由客戶端的TLS來完成的袖外,首先會驗證公鑰是否有效史隆,比如頒發(fā)機構(gòu),過期時間等等曼验,如果發(fā)現(xiàn)異常泌射,則會拋出一個警告头镊,提示證書存在問題。如果證書沒有問題魄幕,那么就生成一個隨機值,然后用證書對該隨機值進(jìn)行加密颖杏。
  5. 傳送加密信息:這部分傳送的是用證書加密后的隨機值纯陨,目的就是讓服務(wù)端得到這個隨機值,以后客戶端和服務(wù)端的通信就可以通過這個隨機值來進(jìn)行加密解密了留储。
  6. 服務(wù)段解密信息:服務(wù)端用私鑰解密后翼抠,得到了客戶端傳過來的隨機值(私鑰),然后把內(nèi)容通過該值進(jìn)行對稱加密获讳。
  7. 傳輸加密后的信息:這部分信息是服務(wù)段用私鑰加密后的信息阴颖,可以在客戶端被還原。
  8. 客戶端解密信息:客戶端用之前生成的私鑰解密服務(wù)段傳過來的信息丐膝,于是獲取了解密后的內(nèi)容量愧。整個過程第三方即使監(jiān)聽到了數(shù)據(jù),也束手無策帅矗。

Alamofire的安全認(rèn)證

證書驗證模式

如果使用的是自簽證書需要我們進(jìn)行安全認(rèn)證偎肃,如果是CA機構(gòu)頒發(fā)的證書是不需要我們寫安全認(rèn)證的相關(guān)代碼的。
舉個Alamofire發(fā)起HTTPS請求的栗子??:

let serverTrustPlolicies: [String: ServerTrustPolicy] = [
    hostUrl: .pinCertificates(
        certificates: ServerTrustPolicy.certificates(),
        validateCertificateChain: true,
        validateHost: true)
]
self.sessionManager = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPlolicies))
self.sessionManager?.request(urlString).response { (defaultResponse) in
    print(defaultResponse)
}
  • 使用起來也是非常的簡單浑此,跟HTTP相比累颂,只是在初始化SessionManager的時候傳入了一個ServerTrustPolicyManager對象,它是證書信任策略的管理者凛俱。
  • 初始化ServerTrustPolicyManager對象的時候紊馏,傳入了一個[String: ServerTrustPolicy]類型的集合作為參數(shù)并保存,key是主機地址,value是驗證模式蒲犬。
public enum ServerTrustPolicy {
    case performDefaultEvaluation(validateHost: Bool)
    case performRevokedEvaluation(validateHost: Bool, revocationFlags: CFOptionFlags)
    case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
    case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)
    case disableEvaluation
    case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool)
}
  • Alamofire 安全認(rèn)證策略的六種模式朱监,其中最常用的有這三種:.pinCertificates 證書驗證模式、.pinPublicKeys 公鑰驗證模式和 .disableEvaluation 不驗證模式暖哨。
    • .performDefaultEvaluation 默認(rèn)策略赌朋,只有合法證書才能通過驗證
    • .performRevokedEvaluation 對注銷證書做的一種額外設(shè)置
    • .pinCertificates 證書驗證模式,代表客戶端會將服務(wù)器返回的證書和本地保存的證書中的 所有內(nèi)容 全部進(jìn)行校驗篇裁,如果正確沛慢,才繼續(xù)執(zhí)行。
    • .pinPublicKeys 公鑰驗證模式达布,代表客戶端會將服務(wù)器返回的證書和本地保存的證書中的 PublicKey部分 進(jìn)行校驗团甲,如果正確,才繼續(xù)執(zhí)行黍聂。
    • .disableEvaluation 該選項下驗證一直都是通過的躺苦,無條件信任身腻。
    • .customEvaluation 自定義驗證,需要返回一個布爾類型的結(jié)果匹厘。
  • 前面的例子就是使用的.pinCertificates證書驗證模式嘀趟。它有三個關(guān)聯(lián)值:
    • 參數(shù)1:certificates代表的是證書
    • 參數(shù)2:validateCertificateChain代表是否驗證證書鏈
    • 參數(shù)3:validateHost代表是否驗證子地址
  • 那么,我們怎么找到項目中的證書呢愈诚?Alamofire為我們提供了一個方法ServerTrustPolicy.certificates()方便使用她按。
public static func certificates(in bundle: Bundle = Bundle.main) -> [SecCertificate] {
    var certificates: [SecCertificate] = []

    let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in
        bundle.paths(forResourcesOfType: fileExtension, inDirectory: nil)
    }.joined())

    for path in paths {
        if
            let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData,
            let certificate = SecCertificateCreateWithData(nil, certificateData)
        {
            certificates.append(certificate)
        }
    }

    return certificates
}
  • 默認(rèn)是在Bundle.main中查找,我們也可以指定存放證書的Bundle來查找炕柔。
  • 了解了驗證模式之后酌泰,來看看驗證的流程。當(dāng)發(fā)起請求之后匕累,會回調(diào)URLSessionTaskDelegate的下面這個方法陵刹。
open func urlSession(
    _ session: URLSession,
    task: URLSessionTask,
    didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
    guard taskDidReceiveChallengeWithCompletion == nil else {
        taskDidReceiveChallengeWithCompletion?(session, task, challenge, completionHandler)
        return
    }

    if let taskDidReceiveChallenge = taskDidReceiveChallenge {
        let result = taskDidReceiveChallenge(session, task, challenge)
        completionHandler(result.0, result.1)
    } else if let delegate = self[task]?.delegate {
        delegate.urlSession(
            session,
            task: task,
            didReceive: challenge,
            completionHandler: completionHandler
        )
    } else {
        urlSession(session, didReceive: challenge, completionHandler: completionHandler)
    }
}
  • 如果taskDidReceiveChallengeWithCompletion有值的話,直接回調(diào)這個閉包欢嘿,這就說明我們可以自己實現(xiàn)驗證的邏輯衰琐。可見Alamofire是非常的友好的际插,既提供了常規(guī)實現(xiàn)方式碘耳,也支持開發(fā)者自定義實現(xiàn)。

  • 繼續(xù)跟蹤代碼進(jìn)入到delegate.urlSession方法里面

  • 然后會執(zhí)行紅框所示的方法框弛,獲取的是前面設(shè)置的驗證模式,然后執(zhí)行evaluate方法

public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool {
    var serverTrustIsValid = false

    switch self {
    // 省略無法代碼.....
    case let .pinCertificates(pinnedCertificates, validateCertificateChain, validateHost):
        if validateCertificateChain {
            let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
            SecTrustSetPolicies(serverTrust, policy)

            SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates as CFArray)
            SecTrustSetAnchorCertificatesOnly(serverTrust, true)

            serverTrustIsValid = trustIsValid(serverTrust)
        } else {
            let serverCertificatesDataArray = certificateData(for: serverTrust)
            let pinnedCertificatesDataArray = certificateData(for: pinnedCertificates)

            outerLoop: for serverCertificateData in serverCertificatesDataArray {
                for pinnedCertificateData in pinnedCertificatesDataArray {
                    if serverCertificateData == pinnedCertificateData {
                        serverTrustIsValid = true
                        break outerLoop
                    }
                }
            }
        }
    // 省略無法代碼.....
    return serverTrustIsValid
}
  • 如果需要驗證證書鏈的話辛辨,會執(zhí)行if代碼塊,大概步驟如下:
    • SecPolicyCreateSSL:創(chuàng)建策略瑟枫,是否驗證host
    • SecTrustSetPolicies:為待驗證的對象設(shè)置策略
    • trustIsValid:進(jìn)行驗證斗搞,成功返回YES
  • 如果需要驗證證書鏈的話,會先把服務(wù)器證書和自己的證書處理成證書二進(jìn)制數(shù)組慷妙,然后進(jìn)行對比僻焚。
  • 如果驗證通過就可以開始正常請求數(shù)據(jù),驗證失敗就會終止請求膝擂,拋出異常虑啤。

公鑰驗證模式

  • 公鑰驗證模式跟證書驗證模式使用起來差不多,使用 .pinPublicKeys 這個枚舉值架馋,第一個參數(shù)傳公鑰狞山,其他參數(shù)和證書驗證模式一樣。Alamofire同樣為我們提供了獲取公鑰的方法ServerTrustPolicy.publicKeys()叉寂,直接調(diào)用這個方法即可萍启。
  • 那么什么時候使用公鑰驗證模式呢?使用公鑰驗證模式的好處是:只要公鑰不變,就可以一直使用勘纯,不用更新證書局服,不用擔(dān)心證書過期了。

總結(jié)

這篇文章大概聊了一下HTTPS的加密流程驳遵,以及通過如何Alamofire請求HTTPS的接口和網(wǎng)絡(luò)挑戰(zhàn)流程的分析淫奔。當(dāng)然,如果想徹底明白HTTPS的實現(xiàn)方式堤结,還需繼續(xù)學(xué)習(xí)搏讶。推薦一本書《圖解HTTP》,講的非常通俗易懂,文章的幾張圖片也是來源于這本書霍殴。


有問題或者建議和意見,歡迎大家評論或者私信系吩。
喜歡的朋友可以點下關(guān)注和喜歡来庭,后續(xù)會持續(xù)更新文章。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末穿挨,一起剝皮案震驚了整個濱河市月弛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌科盛,老刑警劉巖帽衙,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異贞绵,居然都是意外死亡厉萝,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門榨崩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谴垫,“玉大人,你說我怎么就攤上這事母蛛◆婕簦” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵彩郊,是天一觀的道長前弯。 經(jīng)常有香客問我,道長秫逝,這世上最難降的妖魔是什么恕出? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮筷登,結(jié)果婚禮上剃根,老公的妹妹穿的比我還像新娘。我一直安慰自己前方,他們只是感情好狈醉,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布廉油。 她就那樣靜靜地躺著,像睡著了一般苗傅。 火紅的嫁衣襯著肌膚如雪抒线。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天渣慕,我揣著相機與錄音嘶炭,去河邊找鬼。 笑死逊桦,一個胖子當(dāng)著我的面吹牛眨猎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播强经,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼睡陪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了匿情?” 一聲冷哼從身側(cè)響起兰迫,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎炬称,沒想到半個月后汁果,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡玲躯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年据德,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跷车。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡晋控,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出姓赤,到底是詐尸還是另有隱情赡译,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布不铆,位于F島的核電站蝌焚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏誓斥。R本人自食惡果不足惜只洒,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望劳坑。 院中可真熱鬧毕谴,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至舀武,卻和暖如春拄养,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背银舱。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工瘪匿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人寻馏。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓棋弥,卻偏偏與公主長得像,于是被迫代替她去往敵國和親诚欠。 傳聞我的和親對象是個殘疾皇子嘁锯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容