Alamofire 安全认证

2019-08-28  本文已影响0人  好有魔力

HTTP 网络传输有着被窃听篡改的风险, 在日常开发中大多采用HTTPS 来传输重要的数据,而HTTPS 涉及一系列安全认证,本篇探索Alamofire如何处理安全认证

HTTP 与 HTTPS

在开始探索Alamofire之前,了解下 HTTPHTTPS 的一些知识.

HTTP 与 HTTPS 的网络参考模型

图片来自<<HTTP权威指南>>

验证过程中涉及两个重要的概念:

HTTPS 握手过程

图片来自<<HTTP权威指南>>

证书字段的说明

图片来自<<HTTP权威指南>>

SSL安全握手

SSL 安全握手对应于HTTPS 握手过程的步骤2.这里是SSL安全握手的细节

图片来自<<HTTP权威指南>>

验证证书有效性

以下是验证证书有效性的流程:

Alamofire 如何操作认证过程?

上面的知识了解了,来看看 Alamofire 是如何实现认证过程的操作的.
Alamofire基于 URLSession ,所以先来到 URLSessionDelegate的代理方法

extension SessionDelegate: URLSessionDelegate {
//收到需要认证的请求
   open func urlSession(
        _ session: URLSession,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
    {
        guard sessionDidReceiveChallengeWithCompletion == nil else {
            sessionDidReceiveChallengeWithCompletion?(session, challenge, completionHandler)
            return
        }

        var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
        var credential: URLCredential?

        if let sessionDidReceiveChallenge = sessionDidReceiveChallenge {
            (disposition, credential) = sessionDidReceiveChallenge(session, challenge)
        } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
            let host = challenge.protectionSpace.host

            if  
               //对于特定的host(主机地址),获取服务器信任策略
                let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host),
                //从SSL中获取服务器的事务状态
                let serverTrust = challenge.protectionSpace.serverTrust
            {
               //服务器信任策略对象验证服务器的事务状态
                if serverTrustPolicy.evaluate(serverTrust, forHost: host) {
                    disposition = .useCredential
                    //通过验证则创建URL凭证
                    credential = URLCredential(trust: serverTrust)
                } else {
                    //验证失败就取消
                    disposition = .cancelAuthenticationChallenge
                }
            }
        }
        //通知系统以完成
        completionHandler(disposition, credential)
    }
}

来看看 sessionserverTrustPolicyManager属性:

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)
        }
    }
}

在看ServerTrustPolicyManager:

open class ServerTrustPolicyManager {
  
    public let policies: [String: ServerTrustPolicy]

    public init(policies: [String: ServerTrustPolicy]) {
        self.policies = policies
    }

    open func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
        return policies[host]
    }
}

来到 ServerTrustPolicy,ServerTrustPolicy代码比较长,就不放在一起了,分几个部分来说明.

public enum ServerTrustPolicy {

 //使用默认的服务器信任评估,同时允许控制是否质询主机host
  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)
}

ServerTrustPolicy 是一个枚举类型,还提供了一些方法:

//找到某个bundle下的所有证书
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 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 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
    }

trustIsValid方法:

private func trustIsValid(_ trust: SecTrust) -> Bool {
        var isValid = false

        var result = SecTrustResultType.invalid
       //Security 框架下的方法,执行真正的认证
        let status = SecTrustEvaluate(trust, &result)

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


            isValid = result == unspecified || result == proceed
        }

        return isValid
    }

还有几个获取公钥的方法:

//从bundle 中获取所有证书中的公钥
 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
    }

//私有:从服务器证书中获取公钥
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
    }

Alamofire 自定义认证策略

经过了前面的源码解析以及知识了解,下面的代码就好理解了:

 let serverTrustPlolicies:[String: ServerTrustPolicy] = [
         "host0": .pinCertificates(
                certificates: ServerTrustPolicy.certificates(),
                validateCertificateChain: true,
                validateHost: true),
          "host1": .disableEvaluation,
          "host2": .pinPublicKeys(publicKeys: ServerTrustPolicy.publicKeys(),
                                                validateCertificateChain: true,
                                                validateHost: true)
 ]

 let serverTrustPolicyManager = 
ServerTrustPolicyManager(policies: serverTrustPlolicies)       

 let sessionManger = SessionManager(serverTrustPolicyManager: )

Alamofire 安全认证,你了解了吗?

上一篇下一篇

猜你喜欢

热点阅读