美文网首页
Alamofire 安全认证

Alamofire 安全认证

作者: 好有魔力 | 来源:发表于2019-08-28 16:51 被阅读0次

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

    HTTP 与 HTTPS

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

    HTTP 与 HTTPS 的网络参考模型

    图片来自<<HTTP权威指南>>
    • HTTPHTTPS 在网络模型上的区别在于 HTTPS 多了一个安全层(SSL or TLS). 由于只是在HTTP 底层 多添加了一个安全层,所以使用HTTPS不需要做过多的更改

    • HTTPS 数据在 传递给 TCP 之前要经过 SSL or TLS加密,在这一层有一系列机制来保证通信的安全可靠
      1️⃣证书校验:确定对方身份是否是我们的服务器
      2️⃣数据加密(RSA,对称加密):保证数据不能被破译
      3️⃣数据校验:保证数据没有被篡改

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

    • 数字签名:用来验证报文未被篡改或伪造的校验和(也是要被加密的)
    • 数字证书:由一个可信任组织验证和签发的识别信息, 数字证书并没有单一的标准,现在使用的大多数证书都以一种标准格式X.509 v3. 证书的主要内容如图所示:
      图片来自<<HTTP权威指南>>

    HTTPS 握手过程

    图片来自<<HTTP权威指南>>
    • HTTP 相比 HTTPS 多了步骤2 SSL安全握手步骤5 SSL关闭通知,步骤2 SSL握手涉及一系列校验以及加密解密过程 ,这也是HTTPS性能弱于HTTP的原因 .

    证书字段的说明

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

    SSL安全握手

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

    图片来自<<HTTP权威指南>>
    • 步骤1:客户端向服务器请求证书,证书中包含公钥证书的签名.

    • 步骤3:客户端收到服务器证书时会对服务器证书的签名颁发机构进行检查.如果这个机构是个很有权威的公共签名机构(手机中可能存在许多有权威的签名颁发机构的证书),这时会直接用系统的方法验证签名,这个过程不需要我们来处理.,如果服务器的签名颁发机构不是很有权威的(自签证书),则需要客户端验证证书.

    • 步骤3:证书验证成功后,会随机生成一串数字作为数据对称加密的密匙,并且把对称加密密匙放到报文中,用服务器证书中的公钥对报文进行加密,传递给服务器,服务器用自己的私钥解密拿到对称加密密钥,告知客户端.此时客户端与服务器都有了对称加密的密匙, 之后的数据传输双方都用对称加密密匙进行加解密.

    验证证书有效性

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

    • 日期检测:检查证书的起始日期和结束日期,以确保证书有效
    • 签名颁发者可信度检测: 验证证书的颁发机构,因为任何人都可以生成证书,例如服务器生成的自签证书,还有权威机构颁发的证书.自签证书需要客户端来验证机构是否合法.
    • 签名检测:一旦判定签名是可信的,客户端就要对签名使用签名颁发机构的公开秘钥加密,并将其与校验码进行比较,已查看证书是否被篡改.
    • 站点身份检测: 为防止服务器复制他人证书,或者拦截他人的流量,需要验证证书中的域名与它们所对话的服务器的域名是否匹配,如果不匹配,则连接失败.

    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)
        }
    }
    
    • 在不设置我们自己的处理闭包的情况下(sessionDidReceiveChallenge) Alamofire为我们抽象了验证的总体流程:
      Step 1:获取策略对象
      Step 2:获取远端SSL信息,这个信息中有服务器的证书
      Step 3:用创建的的策略对象执行验证方法
      Step 4:根据验证结果的成功失败创建相应的URLCredential
      Step 5:通知系统调用 completionHandler

    来看看 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]
        }
    }
    
    • ServerTrustPolicyManager 只是保存一个字典,字典中存储了host 以及与host对应的 ServerTrustPolicy.

    来到 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
        }
    
    
    • evaluate 枚举自身的各种情况, 在各种case 下都是处理一些参数 之后调用trustIsValid 私有方法.拿到结果处理之后的逻辑
    • 值得注意的是 case .disableEvaluation 都会返回true,没有经过校验过程,直接信任服务器

    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: )
    
    • 有前面的分析我们知道, 认证的策略是保存在 ServerTrustPolicyManager 中的,所以我们 创建 ServerTrustPolicyManager,并指定我们自己的策略字典,在构造SessionManager时指定 ServerTrustPolicyManager,就可以自定义认证策略.

    • 上面的策略字典指定 host0采用固定证书的认证方式,host1直接信任,host2采用固定公钥的认证方式.

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

    相关文章

      网友评论

          本文标题:Alamofire 安全认证

          本文链接:https://www.haomeiwen.com/subject/ocleectx.html