美文网首页
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 安全认证

    HTTP 网络传输有着被窃听篡改的风险, 在日常开发中大多采用HTTPS 来传输重要的数据,而HTTPS 涉及一系...

  • Alamofire-安全认证

    一、问题探索 if let taskDidReceiveChallenge = taskDidReceiveCha...

  • Alamofire之安全认证

    在网络如此发达的今天,网络安全已经和每个人息息相关了。今天我们就一起来了解一下网络安全方面的相关知识。 一、HTT...

  • Alamofire安全认证策略

    那么我们就先来知道一下 HTTPS 以及加密的相关知识, 毕竟这也是面试比较常问到的点. HTTP 与 HTTPS...

  • Alamofire 学习--安全认证

    一、HTTPS 在学习 Alamofire 的安全认证之前,我们先来了解下 HTTPS。 1、为什么要用 HTTP...

  • Alamofire(7)— 安全认证

    ???Alamofire专题目录,欢迎及时反馈交流 ???Alamofire (1)—— URLSession必备...

  • Alamofire 安全认证ServerTrustPolicy

    前言 在互联网迅速发展的年代,基本上天天都在跟网络打交道。那么,在网络的通讯中怎么保证信息的安全性呢?这篇文章,我...

  • 使用 SSH-Key 登录远程服务器

    ssh 提供两种级别的安全认证: 基于口令的安全认证 基于密钥的安全认证 基于口令的安全认证 需要知道用户名和密码...

  • Alamofire(五) 安全认证之HTTPS证书的使用

    前言 这篇文章主要有几个目的: 简单了解HTTPS和HTTP 熟悉Alamofire是如何使用HTTPS证书请求网...

  • OTP一次性动态密码工具实现

    对于企业内部信息安全或行业安全合规性需求,3A认证、授权、审计是必要的基础安全审查项。认证安全机制要求双因素认证,...

网友评论

      本文标题:Alamofire 安全认证

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