【Alamofire源码解析】15- ServerTrustPolicy

这个文件主要写了如何处理服务器和客户端之间的验证。

1. ServerTrustPolicyManager

在开发过程中,我们可能会请求不同的主机地址,而一个服务器对应一个信任策略,即ServerTrustPolicy,所以需要一个manager来管理。

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

    // 根据主机地址返回对应的信任策略
    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

在通过HTTPS连接服务器时,如果需要认证,ServerTrustPolicy就会评估服务器信任,最终决定服务器信任是否有效和是否建立连接。

1) 提供了6种评估机制

// 执行默认的评估
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)

2) 获取证书和公钥

// 获取证书
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
}

// 从所有证书中取出所有公钥
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) 评估服务器信任

对于需要验证的评估机制,总体思路如下:1)使用SecPolicyCreateSSL创建SecPolicy; 2) 使用SecTrustSetPoliciesSecTrust设置安全策略; 3) 验证是否有效

// 评估服务器信任是否有效
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 { // 需要认证证书链
            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循环设置一个名字
            outerLoop: for serverCertificateData in serverCertificatesDataArray {
                for pinnedCertificateData in pinnedCertificatesDataArray {
                    if serverCertificateData == pinnedCertificateData {
                        // 只要服务器信任的证书和指定的证书有一个匹配,就验证通过
                        serverTrustIsValid = true
                        break outerLoop
                    }
                }
            }
        }
    case let .pinPublicKeys(pinnedPublicKeys, validateCertificateChain, validateHost):
        var certificateChainEvaluationPassed = true

        if validateCertificateChain { // 需要认证证书链
            let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
            SecTrustSetPolicies(serverTrust, policy)

            certificateChainEvaluationPassed = trustIsValid(serverTrust)
        }

        if certificateChainEvaluationPassed { // 通过了证书链认证
            outerLoop: for serverPublicKey in ServerTrustPolicy.publicKeys(for: serverTrust) as [AnyObject] {
                for pinnedPublicKey in pinnedPublicKeys as [AnyObject] {
                    if serverPublicKey.isEqual(pinnedPublicKey) {
                        // 只要服务器信任的公钥和指定的公钥相同,则验证通过
                        serverTrustIsValid = true
                        break outerLoop
                    }
                }
            }
        }
    case .disableEvaluation:
        serverTrustIsValid = true
    case let .customEvaluation(closure):
        serverTrustIsValid = closure(serverTrust, host)
    }

    return serverTrustIsValid
}

// 判断服务器信任是否有效
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

        // 如果结果是unspecified或者proceed,则认为是有效的
        isValid = result == unspecified || result == proceed
    }

    return isValid
}

4) 其他私有的方法

// 提供一个SecTrust,返回对应的证书数据
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)
}
// 把证书转化为数据
private func certificateData(for certificates: [SecCertificate]) -> [Data] {
    return certificates.map { SecCertificateCopyData($0) as Data }
}

// 提供一个SecTrust,返回对应的公钥
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
}

// 从某个证书中取出公钥
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
}

有任何问题,欢迎大家留言!

欢迎加入我管理的Swift开发群:536353151,本群只讨论Swift相关内容。

原创文章,转载请注明出处。谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容