Alamofire学习--安全认证策略ServerTrustPolicy

2,391 阅读6分钟

前言

对于安全敏感的数据来说,在与服务端和Web服务交互时使用安全的HTTPS连接是非常重要的一步。默认情况下,Alamofire会使用苹果安全框架内置的验证方法来验证服务端提供的证书链。虽然保证了证书链是有效的,但是也不能防止中间人攻击,为了减少中间人攻击,处理用户的敏感数据时应该使用安全策略(ServerTrustPolicy)来做证书(certificate)验证。

安全策略

引入安全策略ServerTrustPolicy之前,先来个小例子🌰热热身,不然无法展开啊,先创建一个请求:

let urlString = "https://47.105.168.156:20199/users/bar"
SessionManager.default.request(urlString).response { (responseString) in
    print(responseString)
}

结果呢,来看:

哎呀,出现了错误,(这里已经在plist文件中把Allow Arbitrary Loads设置为YES了哦);那么为什么呢? 因为https://47.105.168.156:20199/users/bar这是一个自签名的地址。(要的就是这种效果😁😁😁)

由于所有的网络请求都会走SessionDelegate的回调,在SessionDelegate.swift文件中就有这样一个方法:

这个方法内部表示如果taskDidReceiveChallenge存在,就说明用户自己来处理证书的事情,否则TaskDelegate会进行任务下发,那么就来到了TaskDelegate.swift
从代码判断逻辑应该不难看出最重要的其实就是let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host),如果这个条件不成立,就会跳出执行,那么这个回调也就直接完成了。

如果点击serverTrustPolicyManager跟进去会发现,它是URLSession的一个关联属性,那么这个serverTrustPolicyManager是在什么时候传入的呢?

既然它是URLSession的一个关联属性,那么根据上面写的例子🌰,先从SessionManager来看,在SessionManager的初始化方法中,你会发现,

呦呵,这么容易就找到了,完美!那么说,我们可以初始化一个带有serverTrustPolicyManagerSessionManager。是的,不过,先来看一下ServerTrustPolicyManager这个类:

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

如果我们把ServerTrustPolicy当做是一个安全策略,就是指对一个服务器采取的策略。那么ServerTrustPolicyManager是对ServerTrustPolicy的管理者。然而在现实环境中,一个APP可能会用到很多不同的主机地址。因此就产生了这样的需求,为每一个主机地址都绑定一个特定的安全策略。因此,ServerTrustPolicyManager需要一个字典来存放这些主机地址,以及对应点的安全策略。在前面我们已经知道ServerTrustPolicyManagerURLSession的一个关联属性,那么它会直接绑定到URLSession

既然安全信任策略有多种,那么显然ServerTrustPolicy是一种枚举类型:

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
    // 自定义策略,返回一个BOOL值
    case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool)
    
    //此处省略代码不知道多少行......
}

了解了上面这些内容,那么下面是不是可以初始化一个SessionManager了:

 let urlString = "https://47.105.168.156:20199/users/bar"
 let serverTrustPolicies: [String: ServerTrustPolicy] = [
            urlString: .pinCertificates(
                certificates: ServerTrustPolicy.certificates(),
                validateCertificateChain: true,
                validateHost: true
        )
    ]
let sessionManager = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))
sessionManager.request(urlString).response { (responseString) in
    print(responseString)
}

这样就可以了。😏😼😼

ServerTrustPolicy

从上面⤴️⤴️⤴️我们已经大概了解了ServerTrustPolicy这个枚举类,在它的6个验证策略中,对于其中两个比较常用的做个了解:

pinCertificates

pinCertificates是证书验证策略,代表客户端会将服务端返回的证书和本地保存的证书中的所有内容 全部进行校验,如果正确验证,才继续执行。

pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)

1️⃣参数1:certificates代表的是证书

2️⃣参数2:validateCertificateChain代表是否验证证书链

3️⃣参数3:validateHost代表是否验证子地址

那么既然需要传入证书,怎么找到这个证书文件呢? 在ServerTrustPolicy中有这样一个方法certificates

这个certificates方法会获取到所有根目录下的证书。

在实际开发中,如果在和服务端的安全连接过程中,需要对服务端进行验证,比较好的办法就是在本地保存一些证书,接着拿到服务器传过来的证书,然后进行对比验证,如果验证成功,就表示可以信任该服务端。从上边的函数中可以看出,Alamofire会在Bundle中查找带有".cer", ".CER", ".crt", ".CRT", ".der", ".DER"后缀的证书

pinPublicKeys

pinPublicKeys 公钥验证策略,表示客户端会将服务端返回的证书和本地保存的证书中的 PublicKey部分 进行校验,如果验证正确,才继续执行。

case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)

1️⃣参数1:publicKeys表示公钥

2️⃣参数2:validateCertificateChain代表是否验证证书链

3️⃣参数3:validateHost 代表是否验证子地址

同样的,ServerTrustPolicy中有这样一个方法publicKeys用来查找所有根目录下证书的公钥

这里说明一下validateCertificateChain验证证书链,如果服务端没有配置好证书链,那么就不能验证证书链,也验证不了,会直接取消验证。

验证流程

上面的内容已经对验证策略了解的差不多了,现在把视线拉回到最开始的时候,我们知道let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host),这个条件不成立才导致验证不成功,现在改变了验证策略之后呢,代码继续走下去看看会发生什么?

在这里evaluate方法会去验证serverTrusthost

evaluate方法代码量有点多,但是看到switchcase语句就知道它是对应不同的验证策略来做不同的处理。evaluate方法会传入两个参数一个是服务器的证书,一个是host,结果返回一个布尔类型。

从上面的部分可以知道,正常的验证策略下,想要完成验证都要遵循三个步骤:

1️⃣:SecPolicyCreateSSL 创建策略,是否验证host

2️⃣:SecTrustSetPolicies 为待验证的对象SecTrust设置策略

3️⃣:trustIsValid进行验证

总结

在实际开发项目中,可能会有很多公司去购买CA证书,在请求的时候可能不需要去验证,但对于自签证书,我们可以根据自己的开发需求进行验证,其中最安全的是证书链加host双重验证。关于Alamofire的安全认证策略ServerTrustPolicy就了解到这里,如有错误,还请指正!