Alamofire(八)-- 安全策略ServerTrustPolicy

引言

在网络请求、通信过程当中,最重要的就是安全了,稍有不慎,被别人截取、攻击,都有可能对本身或者公司带来不可估量的损失,因此,网络安全是尤其重大的。 这篇文章,咱们就来说讲,Alamofire做为一个如此重要的三方库,它的安全策略是怎么设计和使用的。算法

HTTPS

在说到Alamofire的安全策略以前,咱们先来了解一下HTTPS,毕竟Alamofire也须要经过HTTPS进行网络请求通信的。swift

几种协议的介绍与关系

  • HTTPHTTP协议传输的数据都是未加密的(明文),所以使用HTTP协议传输隐私信息很是不安全。
  • HTTPS:为了保证隐私数据能加密传输,采用SSL/TLS协议用于对HTTP协议传输的数据进行加密,也就是HTTPS
  • SSLSSL(Secure Sockets Layer)协议是由网景公司设计,后被IETF定义在RFC 6101中。
  • TLSTLS能够说是SSL的改进版,实际上咱们如今的HTTPS都是用的TLS协议。

特色

  • HTTPS在传输数据以前须要客户端(浏览器)与服务端(网站)之间进行一次握手,在握手过程当中将确立双方加密传输数据的密码信息。
  • TLS/SSL中使用了非对称加密,对称加密以及HASH算法。其中非对称加密算法用于在握手过程当中加密生成的密码,对称加密算法用于对真正传输的数据进行加密,而HASH算法用于验证数据的完整性。
  • TLS握手过程当中若是有任何错误,都会使加密链接断开,从而阻止了隐私信息的传输。

请求过程

咱们先来看一下这张图(图片来自网络): 数组

看着这张图,接下来咱们来简单分析一下:浏览器

  • 客户端的HTTPS请求首先向服务器发送一条请求,注意,HTTPS请求均是以https开头。
  • 这时候,服务器端就须要一个证书,这个证书既能够是本身经过某些工具生成,也能够是从某些机构获取。若是是经过某些合法机构生成的证书,是不须要进行验证的,同时,这些请求不会触发@objc(URLSession:task:didReceiveChallenge:completionHandler:)代理方法。若是是本身生成的证书,须要在客户端进行验证,且证书中应该包含公钥、私钥。(公钥:公开的,任何人均可以使用该公钥加密数据,只有知道了私钥才能解密数据。私钥:要求高度保密的,只有知道了私钥才能解密用公钥加密的数据。
  • 服务器端把公钥发送给客户端
  • 此时,客户端拿到公钥,这里要注意,拿到公钥后,并不会直接用于加密数据发送,仅仅是客户端给服务器端发送加密数据,还须要服务器端给客户端发送加密数据,所以,咱们须要在客户端与服务器端创建一个安全的通信通道,开启这条通道的密码只有客户端和服务器端知道。而后,客户端会本身生成一个随机数密码,由于这个随机数密码目前只有客户端知道,因此,这个随机数密码是绝对安全的。
  • 再来,客户端用这个随机数密码再经过公钥加密后发送给服务器端,若是被中间人攻击截获了,没有私钥的状况下,他也是没法解密的。
  • 服务器端收到客户端发送的加密数据后,使用私钥把数据解密后,就获取到了这个随机数。
  • 此时此刻,客户端与服务器端的安全通道就已经链接好了,主要目的就是交换随机数,便于服务器使用这个随机数把数据加密后发送到客户端,此间,使用的是对称加密技术(备注:关于对称加密、非对称加密的详细知识网上或者书籍有不少,内容太多,这里就不详细解释了,也解释不完的😅)。
  • 最后,客户端拿到了服务器端的加密数据后,再使用随机数解密,这样,客户端与服务器端就能经过随机数加密发送数据,进行安全的通信了。

总结

HTTPS每次握手其实都是须要时间开销的,因此,不能每次链接都这样走一次,所以,咱们须要使用对称加密数据的方式。 在Alamofire中,主要的工做是对服务器的验证,其自定义的安全策略验证,我猜,也是模仿的上边的这个过程。 另外,在对服务器的验证下,还应该加上域名验证,这样才能更加的安全安全

OK,前戏都已经说完了,接下来,进入主题。服务器

ServerTrustPolicy

在查看ServerTrustPolicy.swift文件的时候,咱们发现,最核心的2个类ServerTrustPolicyManagerServerTrustPolicy。所以,接下来,咱们就分别来讲一说。网络

ServerTrustPolicy

简述

Alamofire中,ServerTrustPolicy是一个枚举类型:app

public enum ServerTrustPolicy {
    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)
}
复制代码

注意: 这些选项并非函数,只是不一样的类型加上了关联值而已。函数

函数说明

获取证书

首先,看下获取证书的函数方法:工具

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
    }
复制代码
  • 若是在和服务器的安全链接中,须要对服务器进行验证,一个好的方法就是在本地工程保存一些证书,获得服务器传过来的证书后进行对比,若是有匹配,则表示能够信任该服务器。其中包括带有这些后缀的证书:".cer", ".CER", ".crt", ".CRT", ".der", ".DER"
  • 函数中,paths保存的是这些证书的路径,再经过map函数转换为路径,最后,根据这些路径获取证书数据。

获取公钥

获取公钥的函数方法:

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
    }
复制代码

在本地证书中取出公钥,其中又调用了另一个函数方法publicKey(for: certificate),注意到,获取SecKey能够经过SecCertificate方式,也能够经过SecTrust方式。

经过SecTrust获取SecKey

先看一下函数方法:

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
    }
复制代码

很简单的,没有什么好说的,都是固定的写法。

经过SecCertificate获取SecKey

先看一下函数方法:

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
    }
复制代码
  • 同样的,固定写法,只是要特别注意一下SecPolicyCreateBasicX509(),默认是按照X509证书格式来解析的,因此,在生成证书的时候,最好用这个格式来,否则有可能没法得到publicKey
  • 有关X509证书格式的详细说明看这里百度百科

核心方法evaluate

咱们先把函数看一下:

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
    }
复制代码
  • 这个函数很长,一看switch语句,就知道,它的整体思想就是须要根据不一样的策略作出不一样操做。
  • evaluate函数须要接收2个参数,一个是服务器的证书,还有一个是host,返回值是一个bool类型。
  • 由于evaluate函数被定义在枚举中,所以,它确定是依赖枚举的子选项,只有初始化枚举后,才能调用这个函数。
验证步骤说明

从上面的函数能够看到,不论咱们使用哪种策略,要完成验证,都须要如下步骤:

  • SecPolicyCreateSSL:建立策略,是否验证host
  • SecTrustSetPolicies:为待验证的对象设置策略
  • trustIsValid:进行验证
辅助函数
private func trustIsValid(_ trust: SecTrust) -> Bool
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


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

        return isValid
    }
复制代码

该函数用于判断是否验证成功。

private func certificateData(for trust: SecTrust) -> [Data]
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)
    }
复制代码

该函数把服务器的SecTrust处理成证书二进制数组。

private func certificateData(for certificates: [SecCertificate]) -> [Data]
private func certificateData(for certificates: [SecCertificate]) -> [Data] {
        return certificates.map { SecCertificateCopyData($0) as Data }
    }
复制代码

该函数把服务器的SecCertificate处理成证书二进制数组。

策略用法

在下边的验证选项中,咱们能够根据本身的需求进行验证,最安全的是证书链加host双重验证:

  • performDefaultEvaluation:默认的策略,只有合法证书才能经过验证。
  • performRevokedEvaluation:对注销证书作的一种额外设置
  • pinCertificates:验证指定的证书,这里边有一个参数:是否验证证书链,关于证书链的相关内容能够去查一查其余更为详细的资料,验证证书链算是比较严格的验证了。若是不验证证书链的话,只要对比指定的证书有没有和服务器信任的证书匹配项,只要有一个能匹配上,就验证经过
  • pinPublicKeys:这个和上边的那个差很少
  • disableEvaluation:该选项下,验证一直都是经过的,也就是说无条件信任
  • customEvaluation:自定义验证,须要返回一个布尔类型的结果

ServerTrustPolicyManager

简述

ServerTrustPolicyManager这个类是对ServerTrustPolicy的管理类,由于在实际项目开发中,项目中可能会使用不一样的主机地址host,所以,咱们须要为不一样的host绑定一个特定安全策略。

咱们先来看一下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使用了一个字典属性,用来存放有keyvalue对应关系的数据。
  • 因为须要根据host来读取策略,所以,该类增长了serverTrustPolicy方法。

URLSession扩展

先看一下扩展代码:

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做为URLSession的一个属性,是经过运行时的手段来实现。

总结

这篇文章,也只是简单的解析了一下Alamofire中,它的安全策略设计方法,固然,在实际项目开发中,大能够没必要要关心这些实现细节,可是做为一个敬业的、喜欢iOS开发的开发者来讲,仍是颇有必要知晓其中的设计方法、使用方法,不少细节的东西,还须要作不少的功课才行。


常规打广告系列:

简书:Alamofire(八)-- 安全策略ServerTrustPolicy

掘金:Alamofire(八)-- 安全策略ServerTrustPolicy

小专栏:Alamofire(八)-- 安全策略ServerTrustPolicy

相关文章
相关标签/搜索