在网络请求、通信过程当中,最重要的就是安全了,稍有不慎,被别人截取、攻击,都有可能对本身或者公司带来不可估量的损失,因此,网络安全是尤其重大的。 这篇文章,咱们就来说讲,
Alamofire
做为一个如此重要的三方库,它的安全策略是怎么设计和使用的。算法
在说到Alamofire
的安全策略以前,咱们先来了解一下HTTPS
,毕竟Alamofire
也须要经过HTTPS
进行网络请求通信的。swift
HTTP
:HTTP
协议传输的数据都是未加密的(明文),所以使用HTTP
协议传输隐私信息很是不安全。HTTPS
:为了保证隐私数据能加密传输,采用SSL/TLS
协议用于对HTTP
协议传输的数据进行加密,也就是HTTPS
。SSL
:SSL(Secure Sockets Layer)
协议是由网景公司设计,后被IETF
定义在RFC 6101
中。TLS
:TLS
能够说是SSL
的改进版,实际上咱们如今的HTTPS
都是用的TLS
协议。
HTTPS
在传输数据以前须要客户端(浏览器)与服务端(网站)之间进行一次握手,在握手过程当中将确立双方加密传输数据的密码信息。TLS/SSL
中使用了非对称加密,对称加密以及HASH
算法。其中非对称加密算法用于在握手过程当中加密生成的密码,对称加密算法用于对真正传输的数据进行加密,而HASH
算法用于验证数据的完整性。TLS
握手过程当中若是有任何错误,都会使加密链接断开,从而阻止了隐私信息的传输。
咱们先来看一下这张图(图片来自网络): 数组
看着这张图,接下来咱们来简单分析一下:浏览器
- 客户端的
HTTPS
请求首先向服务器发送一条请求,注意,HTTPS
请求均是以https
开头。- 这时候,服务器端就须要一个证书,这个证书既能够是本身经过某些工具生成,也能够是从某些机构获取。若是是经过某些合法机构生成的证书,是不须要进行验证的,同时,这些请求不会触发
@objc(URLSession:task:didReceiveChallenge:completionHandler:)
代理方法。若是是本身生成的证书,须要在客户端进行验证,且证书中应该包含公钥、私钥。(公钥:公开的,任何人均可以使用该公钥加密数据,只有知道了私钥才能解密数据。私钥:要求高度保密的,只有知道了私钥才能解密用公钥加密的数据。)- 服务器端把公钥发送给客户端
- 此时,客户端拿到公钥,这里要注意,拿到公钥后,并不会直接用于加密数据发送,仅仅是客户端给服务器端发送加密数据,还须要服务器端给客户端发送加密数据,所以,咱们须要在客户端与服务器端创建一个安全的通信通道,开启这条通道的密码只有客户端和服务器端知道。而后,客户端会本身生成一个随机数密码,由于这个随机数密码目前只有客户端知道,因此,这个随机数密码是绝对安全的。
- 再来,客户端用这个随机数密码再经过公钥加密后发送给服务器端,若是被中间人攻击截获了,没有私钥的状况下,他也是没法解密的。
- 服务器端收到客户端发送的加密数据后,使用私钥把数据解密后,就获取到了这个随机数。
- 此时此刻,客户端与服务器端的安全通道就已经链接好了,主要目的就是交换随机数,便于服务器使用这个随机数把数据加密后发送到客户端,此间,使用的是对称加密技术(备注:关于对称加密、非对称加密的详细知识网上或者书籍有不少,内容太多,这里就不详细解释了,也解释不完的😅)。
- 最后,客户端拿到了服务器端的加密数据后,再使用随机数解密,这样,客户端与服务器端就能经过随机数加密发送数据,进行安全的通信了。
HTTPS
每次握手其实都是须要时间开销的,因此,不能每次链接都这样走一次,所以,咱们须要使用对称加密数据的方式。 在Alamofire
中,主要的工做是对服务器的验证,其自定义的安全策略验证,我猜,也是模仿的上边的这个过程。 另外,在对服务器的验证下,还应该加上域名验证,这样才能更加的安全安全
OK,前戏都已经说完了,接下来,进入主题。服务器
在查看
ServerTrustPolicy.swift
文件的时候,咱们发现,最核心的2个类ServerTrustPolicyManager
和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
方式。
先看一下函数方法:
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
}
复制代码
- 同样的,固定写法,只是要特别注意一下
SecPolicyCreateBasicX509()
,默认是按照X509
证书格式来解析的,因此,在生成证书的时候,最好用这个格式来,否则有可能没法得到publicKey
。- 有关
X509
证书格式的详细说明看这里百度百科。
咱们先把函数看一下:
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
:建立策略,是否验证hostSecTrustSetPolicies
:为待验证的对象设置策略trustIsValid
:进行验证
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] {
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] {
return certificates.map { SecCertificateCopyData($0) as Data }
}
复制代码
该函数把服务器的
SecCertificate
处理成证书二进制数组。
在下边的验证选项中,咱们能够根据本身的需求进行验证,最安全的是证书链加host
双重验证:
performDefaultEvaluation
:默认的策略,只有合法证书才能经过验证。performRevokedEvaluation
:对注销证书作的一种额外设置pinCertificates
:验证指定的证书,这里边有一个参数:是否验证证书链,关于证书链的相关内容能够去查一查其余更为详细的资料,验证证书链算是比较严格的验证了。若是不验证证书链的话,只要对比指定的证书有没有和服务器信任的证书匹配项,只要有一个能匹配上,就验证经过pinPublicKeys
:这个和上边的那个差很少disableEvaluation
:该选项下,验证一直都是经过的,也就是说无条件信任customEvaluation
:自定义验证,须要返回一个布尔类型的结果
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
使用了一个字典属性,用来存放有key
、value
对应关系的数据。- 因为须要根据
host
来读取策略,所以,该类增长了serverTrustPolicy
方法。
先看一下扩展代码:
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