Alamofire 安全認證ServerTrustPolicy

前言

在互聯網迅速發展的年代,基本上每天都在跟網絡打交道。那麼,在網絡的通信中怎麼保證信息的安全性呢?這篇文章,咱們就來說講,Alamofire做爲iOS開發中一個很是優秀的網絡請求相關的第三方庫,它的安全策略是怎麼設計和使用的。算法

HTTPS簡介

在切入正題以前,先來簡單的瞭解一下HTTPS相關知識,方便對後面內容的理解。若是你已經瞭解了,能夠直接跳過這一段。swift

爲何使用HTTPS

在之前,咱們用的更多的是HTTP,那麼是什麼緣由蘋果公司也主推咱們使用HTTPS這個更安全請求方式的呢?HTTP存在的問題:api

  1. 通信使用明文,內容容易被竊聽
  2. 不驗證通信雙方的身份,容易被假裝
  3. 沒法驗證數據完整性,可能會被篡改數據

使用 HTTPS 通訊機制能夠有效地防止這些問題。HTTPS 並不是是應用層的一種新協議,只是HTTP通訊接口部分用 SSLTLS 協議代替而已。一般 HTTP 直接跟 TCP 通訊,當使用 SSL 時,則變成先和 SSL 通訊,再由 SSLTCP 通訊了。簡言之,HTTPS 就是身披 SSL 協議這層外殼的 HTTP數組

HTTP+數據加密+身份認證+數據完整性保護=HTTPS安全

HTTPS加密方式

HTTPS 採用混合加密機制,就是共享祕鑰加密(對稱加密)和公開密鑰加密(非對稱加密)二者並用的混合加密機制。在交換密鑰環節使用公開密鑰加密的方式,以後創建通信交換報文階段則使用共享密鑰加密。公開密鑰加密比共享密鑰加密更加安全,那爲何不全用公開密鑰加密?由於與共享祕鑰加密相比,處理起來更復雜、處理速度慢。HTTPS結合了兩種加密方式的優劣來實現一種混合加密的流程。如圖所示:服務器

HTTPS驗證和加密流程

HTTPS有單向認證和雙向認證,原理基本差很少,這裏就講一下單向認證的整個流程,先看一張圖:網絡

  1. 客戶端發起HTTPS請求:客戶端向服務端發送SSL協議版本號、加密算法種類、隨機數等信息。
  2. 服務端的配置:採用HTTPS協議的服務器必需要有一套數字證書,能夠本身製做,也能夠向組織申請。區別就是本身頒發的證書須要客戶端驗證經過,才能夠繼續訪問,而使用受信任的公司申請的證書能夠直接經過。這套證書其實就是一對公鑰和私鑰。
  3. 傳送證書:這個證書其實就是公鑰,只是包含了不少信息,如證書的頒發機構,過時時間等等。同時服務端也會向客戶端發送SSL協議版本號、加密算法種類、隨機數等信息
  4. 客戶端解析證書:這部分工做是由客戶端的TLS來完成的,首先會驗證公鑰是否有效,好比頒發機構,過時時間等等,若是發現異常,則會拋出一個警告,提示證書存在問題。若是證書沒有問題,那麼就生成一個隨機值,而後用證書對該隨機值進行加密。
  5. 傳送加密信息:這部分傳送的是用證書加密後的隨機值,目的就是讓服務端獲得這個隨機值,之後客戶端和服務端的通訊就能夠經過這個隨機值來進行加密解密了。
  6. 服務段解密信息:服務端用私鑰解密後,獲得了客戶端傳過來的隨機值(私鑰),而後把內容經過該值進行對稱加密。
  7. 傳輸加密後的信息:這部分信息是服務段用私鑰加密後的信息,能夠在客戶端被還原。
  8. 客戶端解密信息:客戶端用以前生成的私鑰解密服務段傳過來的信息,因而獲取瞭解密後的內容。整個過程第三方即便監聽到了數據,也一籌莫展。

Alamofire的安全認證

證書驗證模式

若是使用的是自簽證書須要咱們進行安全認證,若是是CA機構頒發的證書是不須要咱們寫安全認證的相關代碼的。 舉個Alamofire發起HTTPS請求的栗子🌰:session

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]類型的集合做爲參數並保存,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 安全認證策略的六種模式,其中最經常使用的有這三種:.pinCertificates 證書驗證模式、.pinPublicKeys 公鑰驗證模式和 .disableEvaluation 不驗證模式。
    • .performDefaultEvaluation 默認策略,只有合法證書才能經過驗證
    • .performRevokedEvaluation 對註銷證書作的一種額外設置
    • .pinCertificates 證書驗證模式,表明客戶端會將服務器返回的證書和本地保存的證書中的 全部內容 所有進行校驗,若是正確,才繼續執行。
    • .pinPublicKeys 公鑰驗證模式,表明客戶端會將服務器返回的證書和本地保存的證書中的 PublicKey部分 進行校驗,若是正確,才繼續執行。
    • .disableEvaluation 該選項下驗證一直都是經過的,無條件信任。
    • .customEvaluation 自定義驗證,須要返回一個布爾類型的結果。
  • 前面的例子就是使用的.pinCertificates證書驗證模式。它有三個關聯值:
    • 參數1:certificates表明的是證書
    • 參數2:validateCertificateChain表明是否驗證證書鏈
    • 參數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
}
複製代碼
  • 默認是在Bundle.main中查找,咱們也能夠指定存放證書的Bundle來查找。
  • 瞭解了驗證模式以後,來看看驗證的流程。當發起請求以後,會回調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有值的話,直接回調這個閉包,這就說明咱們能夠本身實現驗證的邏輯。可見Alamofire是很是的友好的,既提供了常規實現方式,也支持開發者自定義實現。閉包

  • 繼續跟蹤代碼進入到delegate.urlSession方法裏面app

  • 而後會執行紅框所示的方法,獲取的是前面設置的驗證模式,而後執行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
}
複製代碼
  • 若是須要驗證證書鏈的話,會執行if代碼塊,大概步驟以下:
    • SecPolicyCreateSSL:建立策略,是否驗證host
    • SecTrustSetPolicies:爲待驗證的對象設置策略
    • trustIsValid:進行驗證,成功返回YES
  • 若是須要驗證證書鏈的話,會先把服務器證書和本身的證書處理成證書二進制數組,而後進行對比。
  • 若是驗證經過就能夠開始正常請求數據,驗證失敗就會終止請求,拋出異常。

公鑰驗證模式

  • 公鑰驗證模式跟證書驗證模式使用起來差很少,使用 .pinPublicKeys 這個枚舉值,第一個參數傳公鑰,其餘參數和證書驗證模式同樣。Alamofire一樣爲咱們提供了獲取公鑰的方法ServerTrustPolicy.publicKeys(),直接調用這個方法便可。
  • 那麼何時使用公鑰驗證模式呢?使用公鑰驗證模式的好處是:只要公鑰不變,就能夠一直使用,不用更新證書,不用擔憂證書過時了。

總結

這篇文章大概聊了一下HTTPS的加密流程,以及經過如何Alamofire請求HTTPS的接口和網絡挑戰流程的分析。固然,若是想完全明白HTTPS的實現方式,還需繼續學習。推薦一本書《圖解HTTP》,講的很是通俗易懂,文章的幾張圖片也是來源於這本書。


有問題或者建議和意見,歡迎你們評論或者私信。 喜歡的朋友能夠點下關注和喜歡,後續會持續更新文章。

相關文章
相關標籤/搜索