Alamofire 安全認(rèn)證ServerTrustPolicy

前言

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

HTTPS簡(jiǎn)介

在切入正題之前,先來簡(jiǎn)單的了解一下HTTPS相關(guān)知識(shí),方便對(duì)后面內(nèi)容的理解。如果你已經(jīng)了解了,可以直接跳過這一段。

為什么使用HTTPS

在以前,我們用的更多的是HTTP,那么是什么原因蘋果公司也主推我們使用HTTPS這個(gè)更安全請(qǐng)求方式的呢?HTTP存在的問題:

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

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

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

HTTPS加密方式

HTTPS 采用混合加密機(jī)制,就是共享秘鑰加密(對(duì)稱加密)和公開密鑰加密(非對(duì)稱加密)兩者并用的混合加密機(jī)制。在交換密鑰環(huán)節(jié)使用公開密鑰加密的方式,之后建立通訊交換報(bào)文階段則使用共享密鑰加密。公開密鑰加密比共享密鑰加密更加安全,那為什么不全用公開密鑰加密?因?yàn)榕c共享秘鑰加密相比,處理起來更復(fù)雜、處理速度慢。HTTPS結(jié)合了兩種加密方式的優(yōu)劣來實(shí)現(xiàn)一種混合加密的流程。如圖所示:

HTTPS驗(yàn)證和加密流程

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

  1. 客戶端發(fā)起HTTPS請(qǐng)求:客戶端向服務(wù)端發(fā)送SSL協(xié)議版本號(hào)、加密算法種類、隨機(jī)數(shù)等信息。
  2. 服務(wù)端的配置:采用HTTPS協(xié)議的服務(wù)器必須要有一套數(shù)字證書,可以自己制作,也可以向組織申請(qǐng)。區(qū)別就是自己頒發(fā)的證書需要客戶端驗(yàn)證通過,才可以繼續(xù)訪問,而使用受信任的公司申請(qǐng)的證書可以直接通過。這套證書其實(shí)就是一對(duì)公鑰和私鑰。
  3. 傳送證書:這個(gè)證書其實(shí)就是公鑰,只是包含了很多信息,如證書的頒發(fā)機(jī)構(gòu),過期時(shí)間等等。同時(shí)服務(wù)端也會(huì)向客戶端發(fā)送SSL協(xié)議版本號(hào)、加密算法種類、隨機(jī)數(shù)等信息
  4. 客戶端解析證書:這部分工作是由客戶端的TLS來完成的,首先會(huì)驗(yàn)證公鑰是否有效,比如頒發(fā)機(jī)構(gòu),過期時(shí)間等等,如果發(fā)現(xiàn)異常,則會(huì)拋出一個(gè)警告,提示證書存在問題。如果證書沒有問題,那么就生成一個(gè)隨機(jī)值,然后用證書對(duì)該隨機(jī)值進(jìn)行加密。
  5. 傳送加密信息:這部分傳送的是用證書加密后的隨機(jī)值,目的就是讓服務(wù)端得到這個(gè)隨機(jī)值,以后客戶端和服務(wù)端的通信就可以通過這個(gè)隨機(jī)值來進(jìn)行加密解密了。
  6. 服務(wù)段解密信息:服務(wù)端用私鑰解密后,得到了客戶端傳過來的隨機(jī)值(私鑰),然后把內(nèi)容通過該值進(jìn)行對(duì)稱加密。
  7. 傳輸加密后的信息:這部分信息是服務(wù)段用私鑰加密后的信息,可以在客戶端被還原。
  8. 客戶端解密信息:客戶端用之前生成的私鑰解密服務(wù)段傳過來的信息,于是獲取了解密后的內(nèi)容。整個(gè)過程第三方即使監(jiān)聽到了數(shù)據(jù),也束手無策。

Alamofire的安全認(rèn)證

證書驗(yàn)證模式

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

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)
}
  • 使用起來也是非常的簡(jiǎn)單,跟HTTP相比,只是在初始化SessionManager的時(shí)候傳入了一個(gè)ServerTrustPolicyManager對(duì)象,它是證書信任策略的管理者。
  • 初始化ServerTrustPolicyManager對(duì)象的時(shí)候,傳入了一個(gè)[String: ServerTrustPolicy]類型的集合作為參數(shù)并保存,key是主機(jī)地址,value是驗(yàn)證模式。
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 證書驗(yàn)證模式、.pinPublicKeys 公鑰驗(yàn)證模式和 .disableEvaluation 不驗(yàn)證模式。
    • .performDefaultEvaluation 默認(rèn)策略,只有合法證書才能通過驗(yàn)證
    • .performRevokedEvaluation 對(duì)注銷證書做的一種額外設(shè)置
    • .pinCertificates 證書驗(yàn)證模式,代表客戶端會(huì)將服務(wù)器返回的證書和本地保存的證書中的 所有內(nèi)容 全部進(jìn)行校驗(yàn),如果正確,才繼續(xù)執(zhí)行。
    • .pinPublicKeys 公鑰驗(yàn)證模式,代表客戶端會(huì)將服務(wù)器返回的證書和本地保存的證書中的 PublicKey部分 進(jìn)行校驗(yàn),如果正確,才繼續(xù)執(zhí)行。
    • .disableEvaluation 該選項(xiàng)下驗(yàn)證一直都是通過的,無條件信任。
    • .customEvaluation 自定義驗(yàn)證,需要返回一個(gè)布爾類型的結(jié)果。
  • 前面的例子就是使用的.pinCertificates證書驗(yàn)證模式。它有三個(gè)關(guān)聯(lián)值:
    • 參數(shù)1:certificates代表的是證書
    • 參數(shù)2:validateCertificateChain代表是否驗(yàn)證證書鏈
    • 參數(shù)3:validateHost代表是否驗(yàn)證子地址
  • 那么,我們?cè)趺凑业巾?xiàng)目中的證書呢?Alamofire為我們提供了一個(gè)方法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來查找。
  • 了解了驗(yàn)證模式之后,來看看驗(yàn)證的流程。當(dāng)發(fā)起請(qǐng)求之后,會(huì)回調(diào)URLSessionTaskDelegate的下面這個(gè)方法。
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)這個(gè)閉包,這就說明我們可以自己實(shí)現(xiàn)驗(yàn)證的邏輯??梢?code>Alamofire是非常的友好的,既提供了常規(guī)實(shí)現(xiàn)方式,也支持開發(fā)者自定義實(shí)現(xiàn)。

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

  • 然后會(huì)執(zhí)行紅框所示的方法,獲取的是前面設(shè)置的驗(yàn)證模式,然后執(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
}
  • 如果需要驗(yàn)證證書鏈的話,會(huì)執(zhí)行if代碼塊,大概步驟如下:
    • SecPolicyCreateSSL:創(chuàng)建策略,是否驗(yàn)證host
    • SecTrustSetPolicies:為待驗(yàn)證的對(duì)象設(shè)置策略
    • trustIsValid:進(jìn)行驗(yàn)證,成功返回YES
  • 如果需要驗(yàn)證證書鏈的話,會(huì)先把服務(wù)器證書和自己的證書處理成證書二進(jìn)制數(shù)組,然后進(jìn)行對(duì)比。
  • 如果驗(yàn)證通過就可以開始正常請(qǐng)求數(shù)據(jù),驗(yàn)證失敗就會(huì)終止請(qǐng)求,拋出異常。

公鑰驗(yàn)證模式

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

總結(jié)

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


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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容