【Alamofire源碼解析】15- ServerTrustPolicy

這個(gè)文件主要寫(xiě)了如何處理服務(wù)器和客戶(hù)端之間的驗(yàn)證吓懈。

1. ServerTrustPolicyManager

在開(kāi)發(fā)過(guò)程中,我們可能會(huì)請(qǐng)求不同的主機(jī)地址,而一個(gè)服務(wù)器對(duì)應(yīng)一個(gè)信任策略坠七,即ServerTrustPolicy誊稚,所以需要一個(gè)manager來(lái)管理。

open class ServerTrustPolicyManager {
    // 信任策略數(shù)組
    open let policies: [String: ServerTrustPolicy]
    
    初始化方法
    public init(policies: [String: ServerTrustPolicy]) {
        self.policies = policies
    }

    // 根據(jù)主機(jī)地址返回對(duì)應(yīng)的信任策略
    open func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
        return policies[host]
    }
}

URLSession添加serverTrustPolicyManager倒脓。

extension URLSession {
    private struct AssociatedKeys {
        static var managerKey = "URLSession.ServerTrustPolicyManager"
    }

    var serverTrustPolicyManager: ServerTrustPolicyManager? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.managerKey) as? ServerTrustPolicyManager
        }
        set (manager) {
            objc_setAssociatedObject(self, &AssociatedKeys.managerKey, manager, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

2. ServerTrustPolicy

在通過(guò)HTTPS連接服務(wù)器時(shí)渤涌,如果需要認(rèn)證,ServerTrustPolicy就會(huì)評(píng)估服務(wù)器信任把还,最終決定服務(wù)器信任是否有效和是否建立連接实蓬。

1) 提供了6種評(píng)估機(jī)制

// 執(zhí)行默認(rèn)的評(píng)估
case performDefaultEvaluation(validateHost: Bool)

// 執(zhí)行撤銷(xiāo)評(píng)估
case performRevokedEvaluation(validateHost: Bool, revocationFlags: CFOptionFlags)

// 使用證書(shū)來(lái)驗(yàn)證服務(wù)器信任
case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)

// 使用公鑰來(lái)驗(yàn)證服務(wù)器信任
case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)

// 禁用評(píng)估
case disableEvaluation

// 自定義評(píng)估
case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool)

2) 獲取證書(shū)和公鑰

// 獲取證書(shū)
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
}

// 從所有證書(shū)中取出所有公鑰
public static func publicKeys(in bundle: Bundle = Bundle.main) -> [SecKey] {
    var publicKeys: [SecKey] = []

    for certificate in certificates(in: bundle) {
        if let publicKey = publicKey(for: certificate) {
            publicKeys.append(publicKey)
        }
    }

    return publicKeys
}

3) 評(píng)估服務(wù)器信任

對(duì)于需要驗(yàn)證的評(píng)估機(jī)制,總體思路如下:1)使用SecPolicyCreateSSL創(chuàng)建SecPolicy; 2) 使用SecTrustSetPoliciesSecTrust設(shè)置安全策略; 3) 驗(yàn)證是否有效

// 評(píng)估服務(wù)器信任是否有效
public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool {
    var serverTrustIsValid = false

    switch self {
    case let .performDefaultEvaluation(validateHost):
        let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
        SecTrustSetPolicies(serverTrust, policy)

        serverTrustIsValid = trustIsValid(serverTrust)
    case let .performRevokedEvaluation(validateHost, revocationFlags):
        let defaultPolicy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
        let revokedPolicy = SecPolicyCreateRevocation(revocationFlags)
        SecTrustSetPolicies(serverTrust, [defaultPolicy, revokedPolicy] as CFTypeRef)

        serverTrustIsValid = trustIsValid(serverTrust)
    case let .pinCertificates(pinnedCertificates, validateCertificateChain, validateHost):
        if validateCertificateChain { // 需要認(rèn)證證書(shū)鏈
            let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
            SecTrustSetPolicies(serverTrust, policy)

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

            serverTrustIsValid = trustIsValid(serverTrust)
        } else {  // 不需要認(rèn)證證書(shū)鏈
            let serverCertificatesDataArray = certificateData(for: serverTrust)
            let pinnedCertificatesDataArray = certificateData(for: pinnedCertificates)

            // outerLoop:給外面這個(gè)for循環(huán)設(shè)置一個(gè)名字
            outerLoop: for serverCertificateData in serverCertificatesDataArray {
                for pinnedCertificateData in pinnedCertificatesDataArray {
                    if serverCertificateData == pinnedCertificateData {
                        // 只要服務(wù)器信任的證書(shū)和指定的證書(shū)有一個(gè)匹配吊履,就驗(yàn)證通過(guò)
                        serverTrustIsValid = true
                        break outerLoop
                    }
                }
            }
        }
    case let .pinPublicKeys(pinnedPublicKeys, validateCertificateChain, validateHost):
        var certificateChainEvaluationPassed = true

        if validateCertificateChain { // 需要認(rèn)證證書(shū)鏈
            let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
            SecTrustSetPolicies(serverTrust, policy)

            certificateChainEvaluationPassed = trustIsValid(serverTrust)
        }

        if certificateChainEvaluationPassed { // 通過(guò)了證書(shū)鏈認(rèn)證
            outerLoop: for serverPublicKey in ServerTrustPolicy.publicKeys(for: serverTrust) as [AnyObject] {
                for pinnedPublicKey in pinnedPublicKeys as [AnyObject] {
                    if serverPublicKey.isEqual(pinnedPublicKey) {
                        // 只要服務(wù)器信任的公鑰和指定的公鑰相同安皱,則驗(yàn)證通過(guò)
                        serverTrustIsValid = true
                        break outerLoop
                    }
                }
            }
        }
    case .disableEvaluation:
        serverTrustIsValid = true
    case let .customEvaluation(closure):
        serverTrustIsValid = closure(serverTrust, host)
    }

    return serverTrustIsValid
}

// 判斷服務(wù)器信任是否有效
private func trustIsValid(_ trust: SecTrust) -> Bool {
    var isValid = false

    var result = SecTrustResultType.invalid
    let status = SecTrustEvaluate(trust, &result)

    if status == errSecSuccess {
        let unspecified = SecTrustResultType.unspecified
        let proceed = SecTrustResultType.proceed

        // 如果結(jié)果是unspecified或者proceed,則認(rèn)為是有效的
        isValid = result == unspecified || result == proceed
    }

    return isValid
}

4) 其他私有的方法

// 提供一個(gè)SecTrust艇炎,返回對(duì)應(yīng)的證書(shū)數(shù)據(jù)
private func certificateData(for trust: SecTrust) -> [Data] {
    var certificates: [SecCertificate] = []

    for index in 0..<SecTrustGetCertificateCount(trust) {
        if let certificate = SecTrustGetCertificateAtIndex(trust, index) {
            certificates.append(certificate)
        }
    }

    return certificateData(for: certificates)
}
// 把證書(shū)轉(zhuǎn)化為數(shù)據(jù)
private func certificateData(for certificates: [SecCertificate]) -> [Data] {
    return certificates.map { SecCertificateCopyData($0) as Data }
}

// 提供一個(gè)SecTrust酌伊,返回對(duì)應(yīng)的公鑰
private static func publicKeys(for trust: SecTrust) -> [SecKey] {
    var publicKeys: [SecKey] = []

    for index in 0..<SecTrustGetCertificateCount(trust) {
        if
            let certificate = SecTrustGetCertificateAtIndex(trust, index),
            let publicKey = publicKey(for: certificate)
        {
            publicKeys.append(publicKey)
        }
    }

    return publicKeys
}

// 從某個(gè)證書(shū)中取出公鑰
private static func publicKey(for certificate: SecCertificate) -> SecKey? {
    var publicKey: SecKey?

    let policy = SecPolicyCreateBasicX509()
    var trust: SecTrust?
    let trustCreationStatus = SecTrustCreateWithCertificates(certificate, policy, &trust)

    if let trust = trust, trustCreationStatus == errSecSuccess {
        publicKey = SecTrustCopyPublicKey(trust)
    }

    return publicKey
}

有任何問(wèn)題,歡迎大家留言!

歡迎加入我管理的Swift開(kāi)發(fā)群:536353151居砖,本群只討論Swift相關(guān)內(nèi)容虹脯。

原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明出處奏候。謝謝循集!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蔗草,隨后出現(xiàn)的幾起案子咒彤,更是在濱河造成了極大的恐慌,老刑警劉巖咒精,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镶柱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡模叙,警方通過(guò)查閱死者的電腦和手機(jī)歇拆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)范咨,“玉大人查吊,你說(shuō)我怎么就攤上這事『桑” “怎么了逻卖?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)昭抒。 經(jīng)常有香客問(wèn)我评也,道長(zhǎng),這世上最難降的妖魔是什么灭返? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任盗迟,我火速辦了婚禮,結(jié)果婚禮上熙含,老公的妹妹穿的比我還像新娘罚缕。我一直安慰自己,他們只是感情好怎静,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布邮弹。 她就那樣靜靜地躺著,像睡著了一般蚓聘。 火紅的嫁衣襯著肌膚如雪腌乡。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,274評(píng)論 1 300
  • 那天夜牡,我揣著相機(jī)與錄音与纽,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛急迂,可吹牛的內(nèi)容都是我干的影所。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼僚碎,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼猴娩!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起听盖,我...
    開(kāi)封第一講書(shū)人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎裂七,沒(méi)想到半個(gè)月后皆看,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡背零,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年腰吟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徙瓶。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡毛雇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出侦镇,到底是詐尸還是另有隱情灵疮,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布壳繁,位于F島的核電站震捣,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏闹炉。R本人自食惡果不足惜蒿赢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望渣触。 院中可真熱鬧羡棵,春花似錦、人聲如沸嗅钻。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)养篓。三九已至灼擂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間觉至,已是汗流浹背剔应。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人峻贮。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓席怪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親纤控。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挂捻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

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